Переклад українською - Арсеній Чеботарьов - Ніжин 2016

Scala Specification


1. Лексичний синтаксис

Програми Scala пишуться з використанням набору символів Unicode Basic Multilingual Plane (BMP); додаткові символи Unicode наразі не підтримуються. Ця глава визначає два режими лексичного синтаксису Scala, режим Scala та XML режим. кщо не вказане інше, наступні дескриптори токенів Scala посилаються на режим Scala, та літеральні символи ‘c’ посилаються на фрагмент ASCII \u0000  \u007F.

В режимі Scala, виключення Unicode замінюються на відповідні символи Unicode з наданим шістнацятиричним кодом.

UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit
hexDigit      ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’

Щоб побудувати токени, символи визначаються за наступними класами (загальна категорія Unicode надається в дужках):

  1. Проміжні символи. \u0020 | \u0009 | \u000D | \u000A.
  2. Літери, що включають малі літери (Ll), великі літери (Lu), заголовні літери (Lt), інші літери (Lo), літери-цифри (Nl) та два символи,  \u0024 ‘$’ та \u005F ‘_’, що обоє вважаються за великі літери.
  3. Цифри ‘0’ | … | ‘9’.
  4. Дужки ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’.
  5. Символи-роздільники ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’.
  6. Символи-оператори. Це складається з усіх друкованих смволах ASCII \u0020 - \u007F, що не входять до наборів вище, математичні символи (Sm) та інші символи (So).

Ідентифікатори

op       ::=  opchar {opchar}
varid    ::=  lower idrest
plainid  ::=  upper idrest
           |  varid
           |  op
id       ::=  plainid
           |  ‘`’ stringLiteral ‘`’
idrest   ::=  {letter | digit} [‘_’ op]

Є три способи сформувати ідентифікатор. Перше, ідентифікатор може починатись з довільної послідовності літер та цифр. За цім може слідувати символи підкреслення ‘_‘ та інші рядки, що складаються з літер та цифр, або символи операторів. Друге, ідентифікатор може починатись з символа оператора, за яким може слідувати довільна послідовність символів операторів. Ці дві форми називаються простими ідентифікаторами. Нарешті, ідентифікатор може також формуватись з довільного рядка між зворотніми лапками (різні системи можуть накладати деякі обмеження до того, які рядки легальні в якості ідентифікаторів). Самий ідентифікатор складається з усіх символів, за винятком зворотніх лапок. 

Як звичайно, застосовується найбільше співпадіння. Наприклад, рядок 

big_bob++=`def`

розбивається на три ідентифікатори, big_bob, ++=, та def. Правила для порівняння з шаблоном додатково розрізняє між ідентифікаторами змінних, які починаються з малих літер, та ідентифікаторами констант, що ні.

Символ ‘$’ зарезервовано для синтезованих компілятором ідентифікаторів. Користувацькі програми не повинні визначати ідентифікатори, що мають символи ‘$’.

Наступні імена є зарезарвованими словами, замість бути членами id синтаксичного класу  або лексічними ідентифікаторами. 

abstract    case        catch       class       def
do          else        extends     false       final
finally     for         forSome     if          implicit
import      lazy        macro       match       new
null        object      override    package     private
protected   return      sealed      super       this
throw       trait       try         true        type
val         var         while       with        yield
_    :    =    =>    <-    <:    <%     >:    #    @

Оператори Unicode \u21D2 ’ та \u2190 ’, що мають еквіваленти в ASCII => та <-, також зарезервовані, .

Ось приклади ідентифікаторів:

    x         Object        maxIndex   p2p      empty_?
    +         `yield`       αρετη     _y       dot_product_*
    __system  _MAX_LEN_

Коли треба отримати доступ до Java ідентифікаторів, що є зарезервованими словами в Scala, використовуйте рядки в зворотніх лапках. Наприклад, твердження  Thread.yield() є нелегальним, оскільки yield є зарезервованим словом в Scala. Однак наступне є обхідним шляхом: Thread.`yield`()

Символи нового рядка

semi ::= ‘;’ |  nl {nl}

Scala є рядок-орієнтованою мовою, де твердження можуть бути завершені крапкою з комою, або новим рядком. Новий рядок в джерельному тексті Scala трактується як специальний токен “nl”, якщо задовільняються три критерії:

  1. Токен, що безпосередньо передує новому рядку, може завершити твердження. 
  2. Токен, що безпосередньо слідує за новим рядком, може почати твердження.
  3. Токен з'являється в регіоні, де дозволені нові рядки. 

Токени, що можуть завершити твердження, такі: літерали, ідентифікатори та роздільники та зарезервовані слова:

this    null    true    false    return    type    
_       )       ]       }

Токени, що можуть починати твердження, є всі токени Scala,  за винятком наступних роздільників та зарезервованих слів:

catch    else    extends    finally    forSome    match
with    yield    ,    .    ;    :    =    =>    <-    <:    <%
>:    #    [    )    ]    }

Токен case може починати твердження, тільки якщо слідує за токенами  class або object.

Нові рядки дозволені у:

  1. всіх джерельних файлах Scala, за виключення вкладених регіонів, да нові рядки відключені, та
  2. інтервалах між парними токенами фігурних дужок {  }, за виключенням вкладених регіонів, де нові рядки відключені.

Нові рядки відключені у:

  1. інтервалах між парними токенами дужок ( та ), за виключенням вкладених регіонів, де нові рядки дозволені, та 
  2. інтервалах між парними токенами кладратних дужок[ та ], за виключенням вкладених регіонів, де нові рядки дозволені. 
  3. Інтервал між токеном case та його співпадаючим токеном  =>, за винятком вкладених регіонів, що нові рядки дозволені.
  4. Любих регіонах, що аналізуються в режимі XML.

Занотуйте, що фігурні дужки {...} заекрановані в XML, та рядкові літерали не є токенами, і, таким чином, не замикають регіон, де включені нові рядки. 

Звичайно, вставляється тільки один токен nl між двома послідовними токенами не-новими-рядками, що є на різних рядках, навіть якщо між двома токенами більше одного рядка. Однак, якщо два токени розділені щонайменьше одним повністю пустим рядком (тобто рядком, що не містить друкованих символів), тоді вставляються два токени nl.

Граматика Scala (повністю надана тут) містить конструкції, де допустимі опціональні nl, але но додаткові крапки з комою. Це має ефект, що новий рядок на одній з ціх позицій не завершують вираз або твердження. Ці позиції можуть бути підсумовані наступним чином:

Декілька токенів нових рядків допустимі в наступних місцях (зауважте, що крапка з комою замість нового рядка може бути нелегальним в кожному з ціх випадків):

Поодинокий токен нового рядка прийнятний

Токени нового рядка між двома рядками не трактуються як роздільники тверджень.

if (x > 0)
  x = x - 1

while (x > 0)
  x = x / 2

for (x <- 1 to 10)
  println(x)

type
  IntList = List[Int]
new Iterator[Int]
{
  private var x = 0
  def hasNext = true
  def next = { x += 1; x }
}

З додатковим символом нового рядка той же код інтерпретується як створення об'єкта, за яким слідує локальний блок:

new Iterator[Int]

{
  private var x = 0
  def hasNext = true
  def next = { x += 1; x }
}
  x < 0 ||
  x > 10

З додатковим символом нового рядка той же код інтерпретуюється як два вираза:

  x < 0 ||

  x > 10
def func(x: Int)
        (y: Int) = x + y

З додатковим символом нового рядка той же код інтерпретується як визначення абстрактної функції, та синтаксично нелегального твердження:

def func(x: Int)

        (y: Int) = x + y
@serializable
protected class Data { ... }

З додатковим символом нового рядка той же код інтерпретується як атрибут та окреме твердження (що синтаксично нелегально).

@serializable

protected class Data { ... }

Літерали

Є літерали для цілих чисел, чисел з плаваючою крапкою, символів, логічних, символічних та рядків. Синтаксис ціх літералів в кожному випадку такий же, як в Java.

Literal  ::=  [‘-’] integerLiteral
           |  [‘-’] floatingPointLiteral
           |  booleanLiteral
           |  characterLiteral
           |  stringLiteral
           |  symbolLiteral
           |  ‘null’

Цілі літерали

integerLiteral  ::=  (decimalNumeral | hexNumeral)
                       [‘L’ | ‘l’]
decimalNumeral  ::=  ‘0’ | nonZeroDigit {digit}
hexNumeral      ::=  ‘0’ (‘x’ | ‘X’) hexDigit {hexDigit}
digit           ::=  ‘0’ | nonZeroDigit
nonZeroDigit    ::=  ‘1’ | … | ‘9’

Цілі літерали звичайно мають тип Int, або тип Long, якщо за ним слідує суфікс L або l. Значення типу Int є всі цілі числа в межах 231 та 2311, включно. Значення типу Long є всі цілів межах 263 та 2631, включно. Помилки часу компіляції виникають, якщо цілий літерал не підпадає в ці диапазони.

Однак, якщо очікуваний тип pt літерала у виразі є Byte, Short, або Char, та ціле число в межах, що визначені типом, тоді число конвертується до типу pt і тип літерала є pt. Числові диапазони, надані цім типам, наступні:



Byte 27 to 271
Short 215 to 2151
Char 0 to 2161
0          21          0xFFFFFFFF       -42L

Літерали з плаваючою крапкою

floatingPointLiteral  ::=  digit {digit} ‘.’ digit {digit} [exponentPart] [floatType]
                        |  ‘.’ digit {digit} [exponentPart] [floatType]
                        |  digit {digit} exponentPart [floatType]
                        |  digit {digit} [exponentPart] floatType
exponentPart          ::=  (‘E’ | ‘e’) [‘+’ | ‘-’] digit {digit}
floatType             ::=  ‘F’ | ‘f’ | ‘D’ | ‘d’

Літерали з плаваючою крапкою є типу Float, коли за типом з плаваючою крапкою слідує суфікс F або f, та типу Double в іншому порядку. Тип Float складається з усіх 32-бітних значень одинарної точності з плаваючою крапкою, що задовільняють IEEE 754, тоді як Double складається з 64-бітних значень подвійної точності.

Якщо за літералом з плаваючою крапкою в програмі слідує літера, має бути щонайменьше один символ проміжку між двома токенами.

0.0        1e30f      3.14159f      1.0e-100      .1

Фраза 1.toString розкладається на три різні токени: ціле-літерал 1, крапка ., та ідентифікатор toString.

1. не є дійсним літералом з плаваючою крапкою, оскільки відсутня обов'язкова цифра після крапки.

Логічні літерали

booleanLiteral  ::=  ‘true’ | ‘false’

Логічні літерали true та false є членами типу Boolean.

Символьні літерали

characterLiteral  ::=  ‘'’ (printableChar | charEscapeSeq) ‘'’

Символьні літерали є поодиноким символом у лапках. Символ або друкований символ юнікод або описаний як екранована послідовність.

'a'    '\u0041'    '\n'    '\t'

Зауважте, що '\u000A' не є дійсним символьним літералом, оскільки перетворення Unicode відбувається перед розбором літерала, та символ Unicode \u000A (переведення рядка) не є друкованим символом. Замість цього можна використати екрановану послідовність '\n' або восьмирічну послідовність '\12'(дивіться тут).

Літеральні рядки

stringLiteral  ::=  ‘"’ {stringElement} ‘"’
stringElement  ::=  printableCharNoDoubleQuote  |  charEscapeSeq

Літеральний рядок є послідовністю символів в подвійних лапках. Символи є або друкованими символами юнікоду, або визначені як екрановані послідовності. Якщо літеральний рядок містить символ подвійних лапок, він може бути екранований як "\"". Значення рядкового літералу є примірником класу String.

"Hello,\nWorld!"
"This string contains a \" character."

Багато-рядкові літерали String

stringLiteral   ::=  ‘"""’ multiLineChars ‘"""’
multiLineChars  ::=  {[‘"’] [‘"’] charNoDoubleQuote} {‘"’}

Багато-рядковий рядковий літерал є послідовністю символів, заключених в потрійні лапки """ ... """. Послідовність символів довільна, за тим виключенням, що вона може містити три або більше послідовних символів подвійних лапок тільки в самому кінці. Символи не обов'язково мають бути друкованими. Екранування Unicode робить як у всіх інших місцях, але жодне з екранованих послідовностей тут не інтерпретуються.

  """the present string
     spans three
     lines."""

Це буде продукувати рядок:

the present string
     spans three
     lines.

Бібліотека Scala містить допоміжний метод stripMargin, що може бути використаний для видалення проміжків напочатку багато-рядкових літералів. Вираз

 """the present string
   |spans three
   |lines.""".stripMargin

обчислюється до

the present string
spans three
lines.

Метод stripMargin визначений в класі scala.collection.immutable.StringLike. Оскільки існує передвизначене неявне перетворення зі String на StringLike, метод стосується до всіх рядків. 

Екрановані послідовності

Наступні екрановані послідовності різпознаються в символьних та рядкових літералах.

послідовність юнікод ім'я символ
‘\‘ ‘b‘ \u0008 backspace BS
‘\‘ ‘t‘ \u0009 horizontal tab HT
‘\‘ ‘n‘ \u000a linefeed LF
‘\‘ ‘f‘ \u000c form feed FF
‘\‘ ‘r‘ \u000d carriage return CR
‘\‘ ‘"‘ \u0022 double quote "
‘\‘ ‘'‘ \u0027 single quote '
‘\‘ ‘\‘ \u005c backslash \

Символ з Unicode між 0 та 255 може бути також представлено як екрановане восьмиричне, точто як коса '\', за якою слідує послідовність до трьох восьмиричних символів.

Буде помилкою часу компіляції, якщо за символом косої лінії в символьному або рядковому літералі не починається дійсна екранована послідовність. 

Символічні літерали

symbolLiteral  ::=  ‘'’ plainid

Символічний літерал 'x є скороченням для виразу scala.Symbol("x"). Symbol є кейс класом, що визначений наступним чином.

package scala
final case class Symbol private (name: String) {
  override def toString: String = "'" + name
}

Метод apply компанійського об'єкту Symbol кешує слабкі посилання на Symbol, таким чином забезпечуючи, що ідентичні символічні літерали еквівалентні з точки зору еквівалентності посилань. 

Проміжки та коментарі

Токени можуть бути розділені символами-проміжками та/або коментарями. Коментарі слідують в двох формах:

Одно-рядкові коментарі є послідовністю символів, що починаються з // , та продовжуються до кінця рядка.

Багато-рядковий коментар є послідовністю символів між /* та */. Багато-рядкові коментарі можуть бути вкладені, але мають бути вірно вкладеними. Таким чином, коментар як /* /* */ буде відхілений, як такий, що незачинений. 

Режим XML

Щоб дозволити літеральні включення з XML фрагментів, лексічний аналіз перемикається з режиму Scala до режиму XML, коли натрапляє на відкливаючу кутову дужку, ‘<’, за наступних умов: ‘<’ має бути попереджений проміжком, відкритими дужками або фігурними дужками, та безпосередньо за ним має іти символ ім'я XML.

 ( whitespace | ‘(’ | ‘{’ ) ‘<’ (XNameStart | ‘!’ | ‘?’)

  XNameStart ::= ‘_’ | BaseChar | Ideographic // as in W3C XML, but without ‘:’

Сканер перемикається з режиму XML до режиму Scala за наступної умови

Зауважте, що токени Scala конструюються в XML режимі, та що коментарі інтерпретуються як текст.

Наступнє визначення значення використовує XML літерал з двома вбудованими виразами Scala:

val b = 
          <span  class="hljs-type"  style="font-weight: bold;">The</span> <span
 class="hljs-type"  style="font-weight: bold;">Scala</span> <span  class="hljs-type"
 style="font-weight: bold;">Language</span> <span  class="hljs-type"  style="font-weight: bold;">Specification</span>
          {scalaBook.version}
          {scalaBook.authors.mkList("", ", ", "")}
        

2. Ідентифікатори, імена, сфери бачення

Імена в Scala ідентифікують типи, значення, методи та класи, що загалом називаються сутностями. Імена вводяться локальними визначеннями та деклараціями, наслідуванням, положеннями імпорту, або положеннями пакунків, що колективно називаються прив'язками.

Прив'язки різних типів мають визначене для них старшинство:

  1. Визначення та декларації, що є локальними, наслідуваними, або зроблені доступними за допомогою положень package в тій же одиниці компіляції, де трапляється визначення, мають найвищу перевагу. 
  2. Явні імпорти мають наступне старшинство.
  3. Узагальнені імпорти мають слідуючий приорітет. 
  4. Визначення, зроблені доступними за допомогою положення package, не в тій одиниці компіляції, де трапляється визначення, мають найнижчий приорітет.

Існує два різних простору імен, одне для типів та один для термів. Те ж ім'я може позначати тип та терм, в залежності від контексту, де використовується ім'я.

Прив'язка має сферу , в якій визначена єдиним іменем сутність може бути доступною за допомогою простого імені. Сфери вкладені. Прив'язка в деякій внутрішній сфері затінює  прив'язки меньшого приорітету в тій же сфері, так само, як прив'язки такого ж, або нижчого приорітету, у зовнішніх сферах. 

Посилання до некваліфікованого ідентифікатора (типу- або терміну-) x обмежене до унікальної прив'язки, що

Буде помилкою, якщо така прив'язка не існує. Якщо x обмежений до import, тоді просте ім'я x береться еквівалентним до кваліфікованого імені, до якого x відображується положенням import. Якщо x обмежений визначенням або декларацією, тоді x посилається до сутності, введеною цією прив'язкою. В цьому випадку, типом x є типом сутності, на яку він посилається. 

Посилання на кваліфікований ідентифікатор (типу- або терму-),  e.x посилається на член типу T з e , що має ім'я x в тому ж просторі імен, що і ідентифікатор. Буде помилкою, якщо T не є типом значення. Типом e.x є тип члену, на яку посилається в T.

Приклад

Нехай є два наступні визначення об'єктів, з іменами X в пакунках P та Q.

package P {
  object X { val x = 1; val y = 2 }
}

package Q {
  object X { val x = true; val y = "" }
}

Наступна програма ілюструє різні типи прив'язок, та відносини старшості між ними. 

package P {                  // `X' прив'язане через package
import Console._             // `println' прив'язане через узагальнений import
object A {
  println("L4: "+X)          // `X' посилається на `P.X'
  object B {
    import Q._               // `X' прив'язаний через узагальнений import
    println("L7: "+X)        // `X' тут посилається на `Q.X'
    import X._               // `x' та `y' прив'язані через узагальнений import
    println("L8: "+x)        // `x' тут посилається на `Q.X.x'
    object C {
      val x = 3              // `x' прив'язаний локальним визначенням
      println("L12: "+x)     // `x' тут посилається на константу `3'
      { import Q.X._         // `x' та `y' прив'язані через узагальнений import
//      println("L14: "+x)   // посилання на `x' тут неоднозначне
        import X.y           // `y' прив'язане явним import
        println("L16: "+y)   // `y' тут посилається на `Q.X.y'
        { val x = "abc"      // `x' прив'язане локальним визначенням
          import P.X._       // `x' та `y' прив'язані узагальненим import
//        println("L19: "+y) // посилання на `y' тут неоднозначне
          println("L20: "+x) // `x' тут посилаєтся на "abc"
}}}}}}

3. Типи

  Type              ::=  FunctionArgTypes ‘=>’ Type
                      |  InfixType [ExistentialClause]
  FunctionArgTypes  ::=  InfixType
                      |  ‘(’ [ ParamType {‘,’ ParamType } ] ‘)’
  ExistentialClause ::=  ‘forSome’ ‘{’ ExistentialDcl
                             {semi ExistentialDcl} ‘}’
  ExistentialDcl    ::=  ‘type’ TypeDcl
                      |  ‘val’ ValDcl
  InfixType         ::=  CompoundType {id [nl] CompoundType}
  CompoundType      ::=  AnnotType {‘with’ AnnotType} [Refinement]
                      |  Refinement
  AnnotType         ::=  SimpleType {Annotation}
  SimpleType        ::=  SimpleType TypeArgs
                      |  SimpleType ‘#’ id
                      |  StableId
                      |  Path ‘.’ ‘type’
                      |  ‘(’ Types ‘)’
  TypeArgs          ::=  ‘[’ Types ‘]’
  Types             ::=  Type {‘,’ Type}

Ми робимо різницю між першого порядку та конструкторами типів, що сприймають параметри тиів, та віддають типи. Підмножина типів першого порядку називаються типами значень, що представляють набори (першокласних) значень. Типи значень є або конкретними або абстрактними.

Кожне конкретне значення може бути представлене тип класу, тобто покажчиком типу, що посилаєтся на клас або трейт 1, або як складний тип, що представляє перетин типів, можливо з уточнення, що ще обмежує типи своїх членів.

Абстрактні типи даних введені параметрами типів та абстрактними прив'язками типів. Дужки в типах можуть використовуватись для групування.

Типи не-значень захоплюють властивості ідентифікаторів, що не є значеннями. Наприклад, конструктор типу не задає напряму тип значень. Однак, коли конструктор типу застосовується з коректними аргументами типу, він надає тип першого порядку, що може бути типом значення.

Типи не-значень виражені неявно в Scala. Тобто, метод типу описується, записуючи сигнатуру метода, що насправді не є реальним типу, хоча воно породжує відповідний тип методу. Конструктори типу є іншим прикладом, так що можна записати Swap[m[_, _], a,b] = m[b, a], але немає синтаксиса для запису відповідного анонімного типу функції напряму.

Шляхи

Path            ::=  StableId
                  |  [id ‘.’] this
StableId        ::=  id
                  |  Path ‘.’ id
                  |  [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id
ClassQualifier  ::= ‘[’ id ‘]’

Шляхи не є типами самі по собі, але вони можуть бути частиною імен типів, та в цій функції формують центральну роль в системі типів Scala.

Шляхом є одне з наступних.

Стабільний ідентифікатор є шляхом, що завершується на ідентифікатор.

Типи значень

Кожне значення в Scala має тип, що є однією з наступних форм.

Типи сінглтони

SimpleType  ::=  Path ‘.’ type

Тип-синглтон є форма p.type, де p є шляхом, що вказує на значення, що очікувано відповідає до scala.AnyRef. Тип визначає набір значень, що складаються з null та значення, визначеного p.

Стабільний тип є або тип-синглтон, або тип, що декларований як субтип трейту scala.Singleton.

Проекція типу

SimpleType  ::=  SimpleType ‘#’ id

Проекція типу T#x посилається на член типу з ім'ям x типу T.

Визначники типу

SimpleType  ::=  StableId

Визначник типу посилаєтся на іменоване значення типу. Він може бути простим або кваліфікованим. Всі таки визначники типу є скороченнями до проекцій типу. 

Зокрема, некваліфіковане ім'я типу t , де t прив'язане до деякого класу, об'єкту, або пакунку C береться як скорочення до C.this.type#t. Якщо t не є обмеженим до класу, об'єкту або пакунку, тоді t береться як скорочення для ε.type#t.

Кваліфікований визначник типу має форму p.t , де p є шляхом та t є ім'ям типу. Такий визначник типу еквівалентний до проекції типу p.type#t.

Приклад

Деякі визначники типу та їх розширення перелічені нижче. Ми маємо на увазі локальний параметр типу t, значення  maintable з членом типу Node ,та стандартний клас scala.Int,

Визначення Розширення
t ε.type#t
Int scala.type#Int
scala.Int scala.type#Int
data.maintable.Node data.maintable.type#Node


Параметризовані типи

SimpleType      ::=  SimpleType TypeArgs
TypeArgs        ::=  ‘[’ Types ‘]’

Параметризований тип T[T1,,Tn] складається з визначника типу T та типових параметрів T1,,Tn , де n1. T має посилатись на конструктор типу, що сприймає n параметрів типу a1,,an.

Скажімо, параметри типу мають нижчі межі L1,,Ln та вищі межі U1,,Un. Параметризований тип гарно сформований, якщо кожний дійсний параметр типу відповідає до його меж, тобто σLi<:Ti<:σUi де σ є підстановкою [a1:=T1,,an:=Tn].

Приклад параметризованих типів

Візьмемо часткові візначення типів:

class TreeMap[A <: Comparable[A], B] { … }
class List[A] { … }
class I extends Comparable[I] { … }

class F[M[_], X] { … }
class S[K <: String] { … }
class G[M[ Z <: I ], I] { … }

наступні параметризовані типи є гарно сформованими:

TreeMap[I, String]
List[I]
List[List[Boolean]]

F[List, Int]
G[S, String]
Приклад

Беручи до уваги зазначені вище визначення, наступні типи є хворобливо-зформованими:

TreeMap[I]            // недійсне: помилкове число параметрів
TreeMap[List[I], Int] // недійсне: параметр типу не в межах

F[Int, Boolean]       // недійсне: Int не є конструктором типу
F[TreeMap, Int]       // недійсне: TreeMap приймає два параметри,
                      //   F очікує конструктор, що сприймає один
G[S, Int]             // недійсне: S cобмежує свій параметр відповідним до String,
                      // G очікує конструктор типу з параметром, що відповідає Int

Кортежні типи

SimpleType    ::=   ‘(’ Types ‘)’

Кортежний тип (T1,,Tn) є псевдонимом до класу scala.Tuplen[T1, … , Tn], де n2.

Кортежні класи є кейс-класи, чиї поля можуть бути доступні через селектори _1 , … , _n. Їх функціональність абстрагована в відповідному трейті Productn-арний кортежний клас та вироблений трейт визначені щонайменьше як наступне в стандартній бібліотеці Scala (вони можуть також додавати інші методи та реалізувати інші трейти).

case class Tuplen[+T1, … , +Tn](_1: T1, … , _n: Tn)
extends Product_n[T1, … , Tn]

trait Product_n[+T1, … , +Tn] {
  override def productArity = n
  def _1: T1def _n: Tn
}

Анотовані типи

AnnotType  ::=  SimpleType {Annotation}

Анотований тип  T a1,,an приєднує анотації a1,,an до типу T.

Приклад

Наступний тип додає анотацію @suspendable до типу String:

String @suspendable

Складні типи

CompoundType    ::=  AnnotType {‘with’ AnnotType} [Refinement]
                  |  Refinement
Refinement      ::=  [nl] ‘{’ RefineStat {semi RefineStat} ‘}’
RefineStat      ::=  Dcl
                  |  ‘type’ TypeDef
                  |

Складний тип T1 with  with Tn{R} представляє об'єкти з членами, як надано в складових типах T1,,Tn та уточнення {R}. Уточнення {R} містить декларації та визначення типів. Якщо декларація або визначення перекриває декларації або визначення в одному з компонентниї типів T1,,Tn, застосовуються звичайні правила перекриття; інакше про декларацію та визначення кажуть як про “структурні” 2.

В декларації методу в структурному уточненні, тип кожного значення-параметру може тільки посилатись на параметри типів або абстрактні типи, що містяться в уточненні. Тобто він має посилатись на параметр типу самого методу, або на визначення типу в уточненні. Це обмеження не стосується до результуючого типу методу.

Якщо надане уточнення, додається явне пусте уточнення, тобто T1 with  with Tn є скороченням для T1 with  with Tn{}.

Складний тип може також складатись тільки з уточнення {R} без попередніх компонентних типів. такий тип еквівалентний до AnyRef {R}.

Приклад

Наступний приклад показує, як декларувати та використовувати метод, який є паметром-типом, що містить уточнення з структурованими деклараціями.

case class Bird (val name: String) extends Object {
        def fly(height: Int) = …
…
}
case class Plane (val callsign: String) extends Object {
        def fly(height: Int) = …
…
}
def takeoff(runway: Int, r: { val callsign: String; def fly(height: Int) }) = {
  tower.print(r.callsign + " потребує злету на полосі " + runway)
  tower.read (r.callsign + " готовий до злету")
  r.fly(1000)
}
val bird = new Bird("Polly the parrot"){ val callsign = name }
val a380 = new Plane("TZ-987")
takeoff(42, bird)
takeoff(89, a380)

Хоча Bird та Plane не поділяють жодного батьківського класу, окрім Object, параметр r методу takeoff визначений за допомогою уточнення зі структурною декларацією сприймати любий об'єкт, що декларує значенняcallsign та метод fly.

Інфіксні типи

InfixType     ::=  CompoundType {id [nl] CompoundType}

Інфіксний тип T1 op T2 складається з інфіксного оператора op, що застосовується до двох операторів типу T1 та T2. Тип еквівалентний до типу застосування op[T1,T2]. Інфіксний оператор op може бути довільним ідентифікатором.

Всі інфіксні оператори типів мають однаковий приоритет; дужки можуть застосовуватись для групування. Асоціативність типового оператора визначається як для термових операторів: оператори типів, що закінчуються на дві крапки ‘:’ є право-асоциативними; всі інші є ліво-асоциативними.

В послідовності типових інфіксних операторів t0op t1op2opntn, всі оператори op1,,opn повинні мати однакову асоціативність. Якщо вони є ліво-асоціативними, послідовність інтерпретується як ((t0op1t1)op2)opntn, інакше вони інтерпретуються як  t0op1(t1op2(opntn)).

Функціональні типи

Type              ::=  FunctionArgs ‘=>’ Type
FunctionArgs      ::=  InfixType
                    |  ‘(’ [ ParamType {‘,’ ParamType } ] ‘)’

Тип (T1,,Tn)U представляє набір значень функцій, що сприймають аргументи типів T1,,Tn та отримують результат типу U. В випадку тільки одного аргументу типу TU є скороченням для (T)U. Аргумент типу в формі T представляє викликаний-за-ім'ям параметр типу T.

Функціональні типи асоціюються зправа, тобто STU те ж саме, що і S(TU).

Функціональні типи є скороченнями для класових типів, що визначають функції apply. Зокрема, n-арний функціональний тип (T1,,Tn)U є скороченням для класового типу Functionn[T1 , … , Tn, U]. Такі класові типи визначені в бібліотеці Scala для n від 0 до 9 наступним чином.

package scala
trait Function_n[-T1 , … , -Tn, +R] {
  def apply(x1: T1 , … , xn: Tn): R
  override def toString = ""
}

Таким чином, функціональні типи є коваріантними в частині результуючого типу, та контрваріантними за типами аргументів.

Екзистенціальні типи

Type               ::= InfixType ExistentialClauses
ExistentialClauses ::= ‘forSome’ ‘{’ ExistentialDcl
                       {semi ExistentialDcl} ‘}’
ExistentialDcl     ::= ‘type’ TypeDcl
                    |  ‘val’ ValDcl

Екзистенціальний тип має форму T forSome { Q }, де Q є послідовністю декларацій типів.

Нехай t1[tps1]>:L1<:U1,,tn[tpsn]>:Ln<:Un будуть типами, задекларованими в Q (жодні з розділів параметрів типів [ tpsi ]можуть бути відсутніми). Сферою кожного типу ti включає тип T та екзистенціальне твердження Q. Типові змінні  ti кажуть, є зв'язаними в типі T forSome { Q }. Змінні типу, що зустрічаються в типі T , але які не зв'язані в T , кажуть, є вільними в T.

примірник типу з T forSome { Q } є типом σT , де σ є заміщенням над t1,,tn, таким що для кожного i, σLi<:σti<:σUi. Набір значень, позначений екзістенціальним типом T forSome {Q} , є об'єднанням множини значень всіх його примірників типів. 

Сколемізація T forSome { Q } є примірником типу σT, де σ є заміною [t1/t1,,tn/tn], та кожний ti є свіжим абстрактним типом, чия нижня межа є σLi, та верхня межа σUi.

Правила спрощення

Екзістенціальні типи дотримуються таким чотирьом еквівалентностям:

  1. Декілька for-твержень екзестенціального типу можуть бути поєднані. Тобто, T forSome { Q } forSome { Q } еквівалентно до T forSome { Q ; Q}.
  2. Невикористані кваліфікатори можуть бути відкинуті. Тобто, T forSome { Q ; Q}, де жодний з типів, визначених в  Q не посилаються в T або Q, еквівалентно до  T forSome {Q}.
  3. Пуста квантифікація може біти відкинута. Тобто, T forSome { } еквівалентно до T.
  4. Екзістенціальний тип T forSome { Q }, де Q містить твердження type t[tps]>:L<:U , є еквівалентним до типу typeT forSome { Q }, де T отримується з T заміною кожного  коваріантного входження t в T на U, та заміщенням кожного контрваріантного входження t в T на L.

Екзістенціальна квантифікація проти значень

За синтаксичної домовленості, твердження прив'язки в екзістенціальному типі може також містити декларації значення val x: T. Екзістенціальний тип  T forSome { Q; val x: S;Q } трактується як скорочення для типу T forSome { Q; type t <: S with Singleton; Q }, де  t свіжим ім'ям типу T , що отримується з T заміною кожного входження x.type на t.

Синтаксис заповнювача для екзистенціальних типів

WildcardType   ::=  ‘_’ TypeBounds

Scala підтримує синтаксис заповнювача для екзестенціальних типів. Узагальнений тип записується у формі _>:L<:U. Обоє тверджень межі можуть бути відсутні. Якщо нижнє обмежувальне твердження  >:L відсутнє, передбачається >:scala.Nothing. Якщо відсутня верхня межа <:U, передбачаєтся <:scala.Any. Узагальнені типи є скороченням до екзостенціальних квантифікованих змінних типів, де екзистенціальна квантифікація є неявною.

Узагальнений тип має з'являтись як аргумент типу параметризованого типу. Нехай T=p.c[targs,T,targs] буде параметризованим типом, де  targs,targs  може бути пустим, та T є узагальненим типом _>:L<:U. Тоді T є еквівалентним до екзистенціального типу

p.c[targs,t,targs] forSome { type t >: L <: U }

де t є деякою свіжою змінною типу. Узагальнені типи також можуть з'являтись як частини інфіксних типів, функціональних типів, або кортежних типів. Їхнє розширення потім є розширенням в еквіваентному параметризованого типу. 

Приклад

Розглянемо визначення класу

class Ref[T]
abstract class Outer { type T } .

Ось декілька прикладів екзистенціальних типів:

Ref[T] forSome { type T <: java.lang.Number }
Ref[x.T] forSome { val x: Outer }
Ref[x_type # T] forSome { type x_type <: Outer with Singleton }

Останні два типи в цьому списку еквівалентні. Альтернативне формолювання першого тпу вище, з використанням узагальнуючого синтаксису:

Ref[_ <: java.lang.Number]
Приклад

Тип List[List[_]] еквівалентний до екзістенціального типу

List[List[t] forSome { type t }] .
Приклад

Розглянемо коваріантний тип

class List[+T]

Тип

List[T] forSome { type T <: java.lang.Number }

еквівалентний (за правилом спрощення 4 вище) до такого

List[java.lang.Number] forSome { type T <: java.lang.Number }

що, в свою чергу, еквівалентний (за правилами спрощення 2 та 3 вище) до  List[java.lang.Number].

Типи не-значень

Типи, описані в наступному, не представляють набір значень, а також не зустрічаються явно в програмах. Вони вводяться в цій доповіді як внутрішні типи визначених ідентифікаторів.

Типі методів

Метод типів визначається внутрішньо як (Ps)U, де (Ps) є послідовністю імен параметрів та типів  (p1:T1,,pn:Tn) для деякого n0 та U є типом (значення або метода). Цей тип представляє іменовані методи, що сприймає іменовані аргументи p1,,pn типів T1,,Tn, та що повертає результат типу U.

Типи методів асоціюються зправа: (Ps1)(Ps2)U трактується як (Ps1)((Ps2)U).

Спеціальним випадком є типи методів без параметрів. Вони записуються як => T. Методи без параметрів іменують вирази, що пере-обчислюються кожного разу, коли посилаються на ім'я такого методу .

Типи методів не існують як типи значень. Якщо ім'я метода використовується як значення, його тип неявно конвертується до відповідного функціонального типу. 

Приклад

Декларації

def a: Int
def b (x: Int): Boolean
def c (x: Int) (y: String, z: String): String

продукують типізації

a: => Int
b: (Int) Boolean
c: (Int) (String, String) String

Типи поліморфних методів

Тип поліморфного метода позначається внутрішньо як [tps]T , де [tps] є розділом типових параметрів [a1 >: L1 <: U1,,an >: Ln <: Un] для деякого n0, та T є типом (значення або методу). Цей тип представляє іменовані методи, що сприймають типові аргументи S1,,Sn, що задовільняють до нижніх меж L1,,Ln та верхніх обмежень U1,,Un, дають результати типу T.

Приклад

Декларації

def empty[A]: List[A]
def union[A <: Comparable[A]] (x: Set[A], xs: Set[A]): Set[A]

продукують типізації

empty : [A >: Nothing <: Any] List[A]
union : [A >: Nothing <: Comparable[A]] (x: Set[A], xs: Set[A]) Set[A]

Конструктори типів

Конструктор типу представлений внутрішньо, здебільшо як метод поліморфного методу. [± a1 >: L1 <: U1,,±an >: Ln <: Un] T представляє тип, що очікується параметром конструктора типу або прив'язкою абстрактного конструктора типу з відповідним твердженням типового параметру. 

Приклад

Розлянемо цей фрагмент класу Iterable[+X]:

trait Iterable[+X] {
  def flatMap[newType[+X] <: Iterable[X], S](f: X => newType[S]): newType[S]
}

Концептуально, конструктор типу Iterable є ім'ям для анонімного типу [+X] Iterable[X], що може бути переданий до параметра конструктора типу newType у flatMap.

Базові типи та визначення членів

Типи членів класів залежать від шляху, яким посилаються на члени. Центральними тут є три нотації, а саме:

  1. нотація набору базових типів типу T,
  2. нотація типу T в деякому класі C видимого з деякого префіксного типу S,
  3. нотація набору прив'язок члена до деякого типу T.

Ці нотації визначені взаємно рекурсивно наступним чином.

  1. Набір базових типів типу є набір класових типів, наданих таким чином.

  2. Нотація типу T в класі C видимого з деякого префіксу типу S має сенс, тільки якщо префікс типу S має примірник типу класу C в якості базового, скажімо, S#C[T1,,Tn]. Тоді ми визначаємо наступне.

    Якщо T є можливо параметризованим класовим типом, де T' клас визначений в деякому іншому класі D, та S є деякий префіксний тип, тоді ми використовуємо "T видимий з S" як скорочення для "T в D видимий з S".

  3. Прив'язки членів типу T наступні

    1. всі прив'язки d , такі що існує примірник деякого класу  C серед базових типів T , та існує визначення або декларація d в C , така що d отримується з d заміною кожного типу T в d на T в C видимий з T, та
    2. всі прив'язки уточнення типу, якщо він має таке.

Визначення проекції типу S#T є прив'язка члена dT типу T в S. В цьому випадку ми також кажемо, що S#T визначений за допомогою dT.

Відношення між типами

Ми визначаємо два відношення між типами.

Ім'я Символічно Інтерпретація
Еквівалентність TU T та U взаємозамінні у всіх контекстах.
Відповідність T<:U Тип T відповідний до типа U.


Еквівалентність

Еквівалентність () між типами є найменша конгруентність 3 , така, що справедливо наступне:

Відповідність

Відношення відповідності  (<:) є найменьше транзитивне відношення, що задовільняє наступним умовам.

Декларація або визначення в деякому складному типі типу класу C відносить іншу декларацію того ж імені в той же складний тип, або класовий тип C, якщо виконується одне з наступного.

Відношення (<:) формує перед-порядок між типами, тобто є транзитивним та рефлексивним. Найменші вищі обмеження та найбільші нижні обмеження набору типів розуміються як відносні до цього порядку. 

Зауваження

Найменша вища межа або найбільша нижня межа набору типів не завжди існують. Наприклад, розглянемо такі визначення класів. 

class A[+T] {}
class B extends A[B]
class C extends A[C]

Тоді типи A[Any], A[A[Any]], A[A[A[Any]]], ... формують спадаючу послідовність верхніх обмежень для B та C. Найменше вище обмеження може бути безкінечним для цієї послідовності, що не існує як тип Scala. Оскільки випадки як наведений вище загалом неможливо детектувати, компілятор Scala вільний відклонити терм, який має тип, вказаний як найменше вище обмеження, та це обмеження буде більш складним, ніж деякий встановлений компілятором ліміт 5.

Найменьше вище обмеження або найбільше нижнє можуть також не бути унікальними. Наприклад, A with B та B with A обоє є більшим нижнім обмеженням для A та B. Якщоre є декілька найменьших вищих обмежень, компілятор Scala може обрати любий з них. 

Слабка відповідність

В деяких ситуаціях Scala використовує більш загальне відношення відповідності. Тип S слабко відповідає типу T, що записується S<:wT, якщо S<:T, або обоє, S та T є примітивними числовими типами та S передує T в наступній послідовності.

Byte  <:w Short
Short <:w Int
Char  <:w Int
Int   <:w Long
Long  <:w Float
Float <:w Double

Слабке найменьше вище обмеження є найменьшим вищим обмеженням відповідно до слабкої відповідності.

Непостійні типи

Непостійність типу апроксимує можливість того, що параметр типу або абстрактного типу примірнику типу не мають жодних значень не-null. Значення члена або непостійний тип не можуть з'явитись в шляху.

Тип є непостійний, якщо він підпадає до однієї з чотирьох категорій:

Складний тип T1 with … with Tn {R} є непостійний, якщо виконується одна з двох умов.

  1. Один з T2,,Tn є параметром типу або абстрактним типом, або
  2. T1 є абстрактним типом, та або уточнення R або тип Tj для j>1 докладає абстрактний член до складного типу, або 
  3. один з T1,,Tn є типом синглтона.

Тут тип S докладає абстрактний член до типу T , якщо S містить абстрактний член, що також є членом T. Уточнення R докладає абстрактний член до типу T , якщо R містить абстрактну декларацію, що також є членом T.

Визначник типу є непостійний, якщо він є псевдонимо непостійного типу, або він визначає параметр типу або абстрактний тип, що має непостійний тип в якості верхньої межі.

ТИп синглтону p.type є непостійним, якщо підлеглий тип шляху p є непостійний.

Екзістенціальний тип T forSome {Q} є непостійним, якщо T є непостійним.

Очищення типів

Тип називається загальним, якщо він містить аргументи типів або змінні типу. Очищення типу є відображення з (можливо загальних) типів на не-загальні типи. Ми пишемо  |T| для очищення типу T. Відображення очищення визначене наступним чином. 

Домінатор перетину списку типів T1,,Tn обчислюється наступним чином. Нехай Ti1,,Tim буде суб-послідовністю типів Ti, що не є супертипами деякого іншого типу Tj. Якщо суб-послідовність визначення типу Tc, що посилається на клас, що не є трейтом, домінатор перетину є  Tc. Інакше домінатор перетину є є перший елемент суб-послідовності Ti1.


  1. Ми вважаємо, що об'єкти та пакунки також неявно визначають клас (з тим же ім'ям, що і об'єкт або пакунок, але недоступний для користувацьких програм).

  2. Посилання на структурно визначений член (виклик метода або доступ до значення або змінної) може генерувати двоїчний код, що значно повільніший, ніж еквівалентний код для неструктурних методів. 

  3. Конгруентність є відношенням еквівалентності, що закрите під формацією контекстів.

  4. Тип методу я неявним, якщо розділ параметрів, що визначає його, починається з ключового слова implicit.

  5. Поточний компілятор Scala обмежує рівень вкладання параметрізації в таких межах, що він щонайбільше на два рівні глибший, ніж максимальний рівень вкладення типів операндів.


4. Базові декларації та визначення

Dcl         ::=  ‘val’ ValDcl
              |  ‘var’ VarDcl
              |  ‘def’ FunDcl
              |  ‘type’ {nl} TypeDcl
PatVarDef   ::=  ‘val’ PatDef
              |  ‘var’ VarDef
Def         ::=  PatVarDef
              |  ‘def’ FunDef
              |  ‘type’ {nl} TypeDef
              |  TmplDef

Декларація вводить імена та надає їм типи. Воа може формувати частину визначення класу або уточнення в складному типі.

Визначення вводить імена, що позначають терми або типи. Воно може формувати частину визначення об'єкту або класу, або воно може бути локальним відносно блока. Обоє, декларації та визначення, продукують прив'язки , що асоціюють імена типів з визначеннями типів або обмеженнями, і так асоціюються імена термів з типами.

Сферою видимості імені, що вводиться декларацією або визначенням, є вся послідовність тверджень, що містять прив'язку. Однак є обмеження на попередні посилання в блоках: в послідовності тверджень s1sn , що складають блок, якщо просте ім'я в si посилається на сутність, визначену в sj , де ji, тоді для всіх sk між та включаючи si та sj,

Декларації та визначення значень

Dcl          ::=  ‘val’ ValDcl
ValDcl       ::=  ids ‘:’ Type
PatVarDef    ::=  ‘val’ PatDef
PatDef       ::=  Pattern2 {‘,’ Pattern2} [‘:’ Type] ‘=’ Expr
ids          ::=  id {‘,’ id}

Декларація значення val x:T вводить x як ім'я для значення типу T.

Визначення значення val x:T = e визначає x як ім'я для значення, що є результатом обчислення e. Якщо визначення значення не рекурсивне, тип T можна опустит, в якому разі приймається упакований тип виразу e. Якщо наданий тип T, тоді очікуєтья, що e відповідатиме йому.

Обчислення визначення значення має на увазі обчислення правої сторони e, за винятком коли він має модифікатор lazy. Ефектом визначенн значення є прив'язка x до значення e , приведеного до типу T. Визначення lazy значення обчислює праву сторону e при першому доступі до значення.

Визначення значення константи має форму

final val x = e

де e є виразом константи. Модифікатор final мусить бути присутнім, та анотація типу не може бути надана. Посилання на значення константи x саме трактується як вираз константи; в згенерованому коді вони замінюються на праву сторону e визначення.

Визначення значень можуть альтернативно мати шаблон в якості лівої сторони. Якщо p є деяким шаблоном, іншим ніж просте ім'я, або ім'я, за яким слідує дві крапки та тип, тоді визначення значення val p = e розширюється до наступного:

val $x = e match {case p => (x1,,xn)}
val x1 = $x._1

val xn = $x._n  .

Тут $x є свіжим ім'ям.

val x = e match { case p => x }
e match { case p => ()}
Приклад

Наступне є прикладами визначень типу

val pi = 3.1415
val pi: Double = 3.1415   // еквівалентно першому визначенню
val Some(x) = f()         // визначення з шаблоном
val x :: xs = mylist      // визначення з інфіксним шаблоном

Останні два визначення мають наступні розширення.

val x = f() match { case Some(x) => x }

val x$ = mylist match { case x :: xs => (x, xs) }
val x = x$._1
val xs = x$._2

Ім'я кожного задекларованого або визначеного значення не може закінчуватись на _=.

Декларація значення val x1,,xn:T є скороченням для послідовності декларацій значень val x1:T;...; val xn:T. Визначення значення val p1,,pn= e є скороченням для послідовності визначень val p1= e;...; val pn= e. Визначення значення val p1,,pn:T = e є скороченням для послідовності визначень значень val p1:T= e;...; val pn:T = e.

Декларації та визначення змінних

Dcl            ::=  ‘var’ VarDcl
PatVarDef      ::=  ‘var’ VarDef
VarDcl         ::=  ids ‘:’ Type
VarDef         ::=  PatDef
                 |  ids ‘:’ Type ‘=’ ‘_’

Декларація змінної var x:T є еквівалентною до декларації обох, функції геттера(отримувача, від getter) x  тa функції сеттера (встановлювача, від setter) x_=:

def x: T
def x_= (y: T): Unit

Реалізація класа може визначити задекларовану змінну з використанням визначення змінної, або визначенням відповідних методів сеттера та геттера.

Визначення змінної var x:T = e вводить несталу змінну з типом T та початковим значенням, що надає вираз e. Тип T може бути опущений, в якому випадку приймається тип e. Якщо тип T наданий, тоді очікується, що e відповідає йому.

Визначення змінної альтернативно можуть мати шаблон в якості лівої частини. Визначення змінної var p = e , де p є шаблоном, іншим від простого імені або імені, за яким слідує дві крапки та тип, розширюється тим же чином, як і визначення значення val p = e, за винятком того, що вільні імена в p вводяться як несталі змінні, а не значення.

Ім'я кожної задекларованої або визначеної змінної не може закінчуватись на _=.

Визначення змінної var x:T = _ може з'явитись тільки як член шаблону. Воно вводить змінне поле з типом T, та початковим значенням по замовчанню. Значення по замовчанню залежить від типу T наступним чином:

замовчання тип T
0 Int або один з його типів-піддиапазонів
0L Long
0.0f Float
0.0d Double
false Boolean
() Unit
null всі інші типи

Коли вони з'являються як члени шаблону, обоє форми визначення змінної також вводять функцію геттера x , що повертає значення, наразі присвоєне змінній, так само як і функцію сеттера x_= , що змінює значення, наразі присвоєне змінній. Функції мають ти ж сигнатури, як і для декларацій змінних. Шаблон матиме ці функції геттера та сеттера як члени, в той час як оригінальна змінна не може бути доступна напряму як член шаблону. 

Приклад

Наступний приклад показує як в Scala можуть бути симульовані властивості. Він визначає клас TimeOfDayVar зі змінними часу, де оновлювані цілі поля представляють години, хвилини та секунди. Їх реалізація містить перевірки, що дозволяють присвоїти цім полям тільки легальні значення. Користувацький код, з іншого боку, має доступ до ціх полів як до звичайних змінних.

class TimeOfDayVar {
  private var h: Int = 0
  private var m: Int = 0
  private var s: Int = 0

  def hours              =  h
  def hours_= (h: Int)   =  if (0 <= h && h < 24) this.h = h
                            else throw new DateError()

  def minutes            =  m
  def minutes_= (m: Int) =  if (0 <= m && m < 60) this.m = m
                            else throw new DateError()

  def seconds            =  s
  def seconds_= (s: Int) =  if (0 <= s && s < 60) this.s = s
                            else throw new DateError()
}
val d = new TimeOfDayVar
d.hours = 8; d.minutes = 30; d.seconds = 0
d.hours = 25                  // підіймається виключення DateError

Декларація змінної var x1,,xn:T є скороченням для послідовності декларацій змінних var x1:T;...; var xn:T. Визначення змінної  var x1,,xn = e є скороченням для послідовності визначень змінних var x1= e;...; var xn= e. Визначення змінної varx1,,xn:T = e є скороченням для послідовності визначень змінних varx1:T = e;...; var xn:T = e.

Декларації типів та псевдоними типів

Dcl        ::=  ‘type’ {nl} TypeDcl
TypeDcl    ::=  id [TypeParamClause] [‘>:’ Type] [‘<:’ Type]
Def        ::=  ‘type’ {nl} TypeDef
TypeDef    ::=  id [TypeParamClause] ‘=’ Type

Декларація типу type t[tps] >: L <: U декларує t як абстрактний тип з нижнім обмежувальним типом L та верхнім обмужувальним типом U. Якщо твердження параметру типу [tps] відсутнє, t абстрагує тип першого порядку, інакше t означає конструкутор типу, що сприймає аргументи типу, як описано в пункті параметрів типу.

Якщо декларація типу з'являється як член-декларація типу, реалізації типу можуть реалізувати t за допомогою любого типу T , для якого L<:T<:U. Виникне помилка часу компіляції, якщо L не відповідає до U. Кожне, або обоє обмежень, можуть бути опущені. Якщо нижня межа L відсутня, вважається що це тип scala.Nothing. Якщо відсутня вища межа U, вважається що це тип scala.Any.

Декларація конструктора типу накладає додаткові обмеження на конкретні типи, які можуть стояти за  t. Окрім обмежень L та U, в частині параметру типу можуть накладатись обмеження вищого порядку, а також варіативність, як визначено у відповідності конструкторів типу. 

Сфера видимості параметру типу розширюється за межі  >: L <: U, та самий вираз параметру типу tps. Вираз типу параметру вищого порядку (абстрактного конструктора типу tc) має той же тип видимості, обмежену до декларації параметру типу tc.

Щоб продемонструвати вкладені сфери, всі наступні декларації еквівалентні: type t[m[x] <: Bound[x], Bound[x]], type t[m[x] <: Bound[x], Bound[y]] та type t[m[x] <: Bound[x], Bound[_]], позаяк сфера дії, тобто типу параметру m , є обмеженою до декларації m. В усіх з них, t є членом абстрактного типу, що абстраговано над двома конструкторами типу: m означає конструктор типу, що сприймає один параметр типу, та що має бути підтипом Bound, другий параметр конструктора типу t , t[MutableList, Iterable] є дійсним використанням t.

Псевдоним типу type t = T визначає t як ім'я-псевдонім для типу T. Ліва сторона псевдоніму типу, тобто type t[tps] = T. Сфера параметру типу розширюється над правою частиною T , та самим твердженням параметра типу tps.

Правила сфери видимості для визначень and параметрів типу роблять можливим, щоб ім'я типу з'являлось у власному обмеженні в його правій частині. Однак буде статичною помилкою, якщо псевдоним типу посилається рекурсивно на сам визначений конструктор типу. Тобто, тип T в псевдонимі типу type t[tps] = T не може посилатись прямо або непрямо на ім'я t. Також буде помилкою, якщо абстрактний тип прямо або непрямо є власним нижнім або верхнім обмеженням.

Приклад

Далі слідують легальні декларації та визначення типів:

type IntList = List[Integer]
type T <: Comparable[T]
type Two[A] = Tuple2[A, A]
type MyCollection[+X] <: Iterable[X]

Наступне нелегальне:

type Abs = Comparable[Abs]      // рекурсивний псевдонім типу

type S <: T                     // S, T обмежені самі собою
type T <: S

type T >: Comparable[T.That]    // Неможливо вживати селектор для T,
                                // T є типом, а не значенням
type MyCollection <: Iterable   // Члени конструктора типу мусять явно 
                                // вказати свої параметри типу.

Якщо псевдонім типу type t[tps] = S посилається на клас типу S, ім'я t може також використовуватись як конструктор для об'єктів типу S.

Приклад

Об'єкт Predef містить визначення, що встановлює Pair як псевдонім для параметризованого класу Tuple2:

type Pair[+A, +B] = Tuple2[A, B]
object Pair {
  def apply[A, B](x: A, y: B) = Tuple2(x, y)
  def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x)
}

Як слідоцтво, для кожних двох типів S та T, тип Pair[S, T] є еквівалентним до типу Tuple2[S, T]. Pair також може бути використаний як конструктор замість Tuple2, як тут, :

val x: Pair[Int, String] = new Pair(1, "abc")

Параметри типів

TypeParamClause  ::= ‘[’ VariantTypeParam {‘,’ VariantTypeParam} ‘]’
VariantTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeParam
TypeParam        ::= (id | ‘_’) [TypeParamClause] [‘>:’ Type] [‘<:’ Type] [‘:’ Type]

Параметри типів з'являються в визначеннях типів, визначеннях класів, та визначеннях функцій. В цьому розділі ми розглянемо тільки визначення параметрів типу з нижнім обмеженням  >:L та верхнім обмеженням  <:U , в той час як дискусія щодо контекстних обмежень :U та обмежень перегляду <%U відкладені до наступного разу.

Найбільш загальною формою параметрів типу першого порядку є @a1@an ±  t         >: L <: U. Тут L та U є нижнім та верхнім межами, що обмежують можливі типи аргументів для параметра. Буде помилкою компіляції, якщо L не відповідає до U. ± є варіантність, тобто опціональний префікс, що є або +, або -. Одна або більше анотацій можуть передувати параметру типу.

Імена всіх параметрів типу мусять бути попарно різними в замикаючому їх твердженні параметрів типу. Сфера дії параметру типу включає в жодному разі ціле твоердження параметру типу. Таким чином, можливо, щоб параметр типу з'являвся як частина власного обмеження, або обмеження для інших параметрів типу, в тому ж твердженні. Однак параметр типу не може бути обмежений прямо або непрямо самим собою.

Параметр конструктора типу додає вкладене твердження параметру типу до параметру типу. В найбільш загальній формі параметр конструктора типу є @a1@an± t[tps] >: L <: U.

Надані вище обмеження дії узагальнені для випадка вкладених визначень параметрі типу. Параметри типу вищого порядку (параметри типу параметрів типу t) видимі тільки в їх безпосередньо оточуючих твердженнях параметрів типу (можливо, включаючи твердження на глибшому рівні вкладення), та в межах t. Таким чином, їх імена мусять бути попарно різними тільки з іменами інших видимих параметрів. Оскільки імена параметрів типу вищих порядків таким чином часто не релевантні, вони можуть бути вказані за допомогою ‘_’, що ніде не видиме.

Приклад

Ось деяки гарно сформовані приклади параметрів типу:

[S, T]
[@specialized T, U]
[Ex <: Throwable]
[A <: Comparable[B], B <: A]
[A, B >: A, C >: A <: B]
[M[X], N[X]]
[M[_], N[_]] // еквівалентно попередньому
[M[X <: Bound[X]], Bound[_]]
[M[+X] <: Iterable[X]]

Наступні приклади параметрів типу є нелегальними:

[A >: A]                  // нелегально, `A' обмежує саме себе
[A <: B, B <: C, C <: A]  // нелегально, `A' має себе за обмеження
[A, B, C >: A <: B]       // нелегально, нижне обмеження `A' для `C' не
                          // відповідає верхньому обмеженню `B'.

Анотації варіантності

Анотації варіантності  індикують, як примірники параметризованих типів варіюють відповідно до суб-типізації. Варіантність ‘+’ вказує на коваріантну залежність, варіантність ‘-’ вказує на контрваріантну залежність, та відсутність варіантності вказує на інваріантну залежність.

Анотація варіантності обмежує шлях, яким анотована змінна циклу може з'являтись в типі або класі, що прив'язує параметр типу. В визначенні типу type T[tps] = S, або в декларації type T[tps] >: L <: U параметри типу, помічені ‘+’, повинні з'являтись тільки в коваріантній позиції, в той час як параметр типу, що помічений ‘-’, має з'являтись тільки в контрваріантній позиції. Аналогічно, для визначення класу class C[tps](ps)extends T { x:S=> ...}, параметр типу, позначений ‘+’, має з'являтись тільки в коваріантній позиції в самому типі S та шаблоні T, тоді як параметр типу, що помічений ‘-’ призначений для контрваріантних позицій.

Позиція варіації параметру типу в типі або в шаблоні визначена наступним чином. Давайте протиставимо коваріантність та контрваріантність, та все це протиставимо інваріантності. Вищий рівіень типу або шаблону завжди в коваріантній позиції. Позиція варіантності змінюється в наступних конструкціях.

Посилання на параметри типу в об'єкт-приватних, або об'єкт-захищених значеннях, типах, змінних, або методах класу не перевіряються щодо їх варіантної позиції. У ціх членах параметр типу може з'являтись будь-де без обмеження його легальної анторації варіантності.

Приклад

Наступна анотація варіантності є легальною.

abstract class P[+A, +B] {
  def fst: A; def snd: B
}

З ціма анотаціями варіантності примірники типу підтипу P коваріантні з точки зору їх аргументів. Наприклад,

P[IOException, String] <: P[Throwable, AnyRef]

Якщо члени P несталі, та ж анотація варіантності стає нелегальною.

abstract class Q[+A, +B](x: A, y: B) {
  var fst: A = x           // **** помилка: нелегальная варіантність:
  var snd: B = y           // `A', `B' з'являється в інваріантній позиції.
}

Якщо несталі змінні є об'єкт-приватними, визначення класу знову стає легальним:

abstract class R[+A, +B](x: A, y: B) {
  private[this] var fst: A = x        // OK
  private[this] var snd: B = y        // OK
}
Приклад

Наступна анотація варіантності є нелегальною, оскільки a з'являєтсья в контрваріантній позиції в параметрі append:

abstract class Sequence[+A] {
  def append(x: Sequence[A]): Sequence[A]
                  // **** помилка: нелегальна варіантність:
                  // `A' з'являється в контрваріантній позиції.
}

Проблеми можна уникнути, генералізуючи тип append за допомогою нижнього обмеження:

abstract class Sequence[+A] {
  def append[B >: A](x: Sequence[B]): Sequence[B]
}
Приклад
abstract class OutputChannel[-A] {
  def write(x: A): Unit
}

З цією анотацією ми маємо, що OutputChannel[AnyRef] відповідає OutputChannel[String]. Таким чином, канал, по якому можна писати будь-який об'єкт, може замінювати канал, по якому ви можете писати тільки рядки. 

Декларації та визначення функцій

Dcl                ::=  ‘def’ FunDcl
FunDcl             ::=  FunSig ‘:’ Type
Def                ::=  ‘def’ FunDef
FunDef             ::=  FunSig [‘:’ Type] ‘=’ Expr
FunSig             ::=  id [FunTypeParamClause] ParamClauses
FunTypeParamClause ::=  ‘[’ TypeParam {‘,’ TypeParam} ‘]’
ParamClauses       ::=  {ParamClause} [[nl] ‘(’ ‘implicit’ Params ‘)’]
ParamClause        ::=  [nl] ‘(’ [Params] ‘)’}
Params             ::=  Param {‘,’ Param}
Param              ::=  {Annotation} id [‘:’ ParamType] [‘=’ Expr]
ParamType          ::=  Type
                     |  ‘=>’ Type
                     |  Type ‘*’

Декларація функції має форму def fpsig:T, де f ім'я функції, psig сигнатура параметру та T  - тип результату. Визначення функції def fpsig: T  = e також включає тіло функції, e, тобто вираз, який визначає результат функції. Сигнатура складається з опціонального параметру типа [tps], за яким слідує нуль або більше значень параметрів (ps1)(psn). Така декларація або визначення вводить значення з (можливо поліморфним) типом методу, чиї параметри типу та результата надані. 

Очікується, що тип тіла функції відповідає до задекларованого типу результату, якщо такий надано. Якщо декларація типу не рекурсивна, тип результата можна опустити, в якому разі він визначається з типу тіла функції. 

Параметр типу tps складається з одного або більше декларацій типу, що вводить параметри типу, можливо з обмеженнями. Сфера дії параметру типу включає цілу сигнатуру, включаючи любе з обмежень типу, а також тіло функції, якщо воно присутнє.

Параметр значення ps складається з нуля або білльше прив'язок формальних параметрів, таких як x: T або  x:T=e, яке пирв'язує значення параметрів з їх типами. 

Аргументи по замовчанню

Кожна декларація значення параметру може додатково визначати аргумент по замовчанню. Вираз аргумента по замовчанню e є типізованим з очікуваним типом T , отриманим заміщень всіх входжень параметрів типу функції T на невизначений тип. 

Для кожного параметра pi,j з аргументом по замовчанню генерується метод f$default$n, що обчислює вираз аргумента по замовчанню. Тут  n означає позицію параметра в декларації метода. Ці методи параметризовані параметром типу [tps], та всі твердження параметрів значень (ps1)(psi1) передують pi,j. Методи f$default$n недоступні для користувацьких програм.

Приклад

В методі

def compare[T](a: T = 0)(b: T = a) = (a == b)

вираз по замовчанню 0 типізований з невизначеним очікуваним типом. Коли застосовується compare(), вставляється значення по замовчанню 0 та T інстанціюється як Int. Методи, що обчислюють аргументи по замовчанню мають форму:

def compare$default$1[T]: Int = 0
def compare$default$2[T](a: T): T = a

Сфера дії формального параметру значення x сягає всіх послідуючих тверджень параметрів, так само, як тип метода та тіло функції, якщо вони присутні. Обоє, імена параметрів типу та імена параметрів значень, мусять бути попарно розбіжними. 

Значення по замовчанню, що залежить від ранішніх параметрів, використовує справжні аргументи, якщо вони надані, не аршументи по замовчанню. 

def f(a: Int = 0)(b: Int = a + 1) = b // OK
// def f(a: Int = 0, b: Int = a + 1)  // "помилка: не знайдено: значення a"
f(10)()                               // повертає 11 (не 1)

Параметри за ім'ям

ParamType          ::=  ‘=>’ Type

Тип значення параметру може може мати префікс =>, тобто x:=> T. Тип такого параметра потім є непараметризованим типом метода => T. Це вказує на те, що відповідний аргумент не обчислюється в точці застосування функції, але замість цього обчислюється при кожному використанні в функції. Тобто аргумент обчислюється з використанням виклик-за-ім'ям.

Модифікатор за-ім'ям недоступний для параметрів класів, що мають префікс val або var, включаючи параметри кейс класів, для яких префікс val генерується неявно. Модифікатор за-ім'ям також не допускається для неявних параметрів.

Приклад

Декларація

def whileLoop (cond: => Boolean) (stat: => Unit): Unit

вказує, що обоє параметри whileLoop обчислюються з використанням виклику-за-ім'ям.

Повторювані параметри

ParamType          ::=  Type ‘*’

Останнє значення параметру розділу параметрів може мати суфікс '*', тобто (..., x:T*). Тип такого повторюваного параметру зсередини методу буде послідовністю типу scala.Seq[T]. Методи з повторюваними параметрами  T* приймають змінне число аргументів типу T. Таким чином, якщо метод m з типом (p1:T1,,pn:Tn,ps:S*)U застосований до аргументів (e1,,ek) , де kn, коли m взятий в цьому застосуванні мав тип  (p1:T1,,pn:Tn,ps:S,,psS)U, з kn входженнями типу S , де любі імена параметрів після ps є свіжими. Одним виключенням з цього правила є випадок, коли останній аргумент маркований як аргумент-послідовність через анотацію типу _*. Якщо m вище застосований до аргументів (e1,,en,e: _*), тоді тип m в цьому застосуванні обирається (p1:T1,,pn:Tn,ps:scala.Seq[S]).

Не допускається визначати жодні аргументи по замовчанню в розділі параметрів з повторюваним параметром. 

Приклад

Наступне визначення метода обчислює суму квадратів змінного числа цілих аргументів.

def sum(args: Int*) = {
  var result = 0
  for (arg <- args) result += arg
  result
}

Наступні застосування цього метода дає 0, 1, 6, в такому порядку.

sum()
sum(1)
sum(1, 2, 3)

Більше того, розглянемо визначення:

val xs = List(1, 2, 3)

Наступне застосування метода sum є хворобливо-сформованим:

sum(xs)       // ***** помилка: очікується: Int, знайдено: List[Int]

Для контрасту, наступне застосування добре сформоване, та знову дає результат 6:

sum(xs: _*)

Процедури

FunDcl   ::=  FunSig
FunDef   ::=  FunSig [nl] ‘{’ Block ‘}’

Для процедур існує спеціальний синтаксис, тобто функцій, що повертають значення Unit (). Декларація процедури є декларацією функції, де тип результата пропущений. Тип результату потім неявно завершується до типу Unit. Тобто, def f(ps) еквівалентно до def f(ps): Unit.

Визначення процедури є визначення функції, де тип результату та знак рівності відсутні; її визначений вираз має бути блоком. Тобто, def f(ps){stats} is еквівалентне до def f(ps): Unit = {stats}.

Приклад

Ось декларація та визначення процедури з ім'ям write:

trait Writer {
  def write(str: String)
}
object Terminal extends Writer {
  def write(str: String) { System.out.println(str) }
}

Код вище неявно завершений до наступного коду:

trait Writer {
  def write(str: String): Unit
}
object Terminal extends Writer {
  def write(str: String): Unit = { System.out.println(str) }
}

Вивід типу повернення методу

Визначення члену класу m, що перекриває деяку іншу функцію m в базовому класі C може не вказувати тип повернення, навіть якщо він рекурсивний. В цьому випадку тип поверненя R перекритої функції m, що видимий як член C, береться як тип повернення для m для кожного рекурсивного виклику m. Таким чином, тип R для правої сторони m може бути визначений, який потім береться як тип, що повертається m. Зауважте, що R може відрізнятись від R, доки R відповідає до R.

Приклад

Розглянемо наступне визначення:

trait I {
  def factorial(x: Int): Int
}
class C extends I {
  def factorial(x: Int) = if (x == 0) 1 else x * factorial(x - 1)
}

Тут є прийнятним не вказувати тип реузльтату factorial в C, навіть при тому, що він рекурсивний .

Твердження import

Import          ::= ‘import’ ImportExpr {‘,’ ImportExpr}
ImportExpr      ::= StableId ‘.’ (id | ‘_’ | ImportSelectors)
ImportSelectors ::= ‘{’ {ImportSelector ‘,’}
                    (ImportSelector | ‘_’) ‘}’
ImportSelector  ::= id [‘=>’ id | ‘=>’ ‘_’]

Твердження імпорту мають форму import p.I , де p є стабільним ідентифікатором та I є імпортабельним виразом. Вираз імпорту визначає набір імен імпортованих членів p , що будуть доступні без кваліфікації. Член  m з p є імпортабельним, якщо він не є об'єкт-приватним. Найбільш загальна форма виразу імпорту є список селекторів імпорту

{ x1 => y1,,xn => yn, _ }

для n0, де фінальне підкреслення ‘_’ може бути відсутнім. Це робить доступним кожний імпортабельний член p.xi під некваліфікованим іменем yi. Тобто, кожний селектор імпорту  xi=> yi перейменовує p.xi на yi. Якщо фінальний підстановочний символ присутній, всі імпортабельні члени z з p , інші ніж x1,,xn,y1,,yn також будуть зроблені доступними під їх власними некваліфікованими іменами.

Селектори імпорту роблять таким же чином для типів та членів термів. Наприклад, твердження імпорту import p.{x => y} перейменовує терм з імені p.x на терм імені y та тип імені p.x на тип імені y. Щонайменьше один з ціх двох імен мусить посилатись на імпортабельний член p.

Якщо ціль селектору імпорту є підстановочний символ, селектор імпорту приховує доступ до первинного члена. Наприклад, селектор імпорту x => _ “перейменовує” x на підстановочний символ (що є недоступним в користувацьких програмах), і таким чином ефективно запобігає некваліфікованому доступу до  x. Це корисне, якщо є фінальний підстановочний символ в тому ж списку селекторів імпорту, що імпортує всі члени, не вказані в попередньоміх селекторах імпорту.

Поле зору прив'язки, що воодиться твердженням імпорту починається  беспосередньо після цього твердження імпорту, та продовжуєтьсядо кінця охоплюючого блоку, шаблону, твердження пакунку, або одиниці компіляції, що настане першим.

Існують декільки скорочень. Селектор імпорту може бути тільки простим ім'ям x. В цьому випадку x імпортується без переіменування, так що селектор імпорту еквівалентний до x => x. Більше того, можливо замінити цілий список селекторів імпорту на один ідентифікатор або символ підстановки. Твердження імпорту import p.x is еквівантне до import p.{x}, тобто воно робить доступним без кваліфікації член x з p. Твердження імпорту import p._ є еквівалентним до import p.{_}, тобто воно робить доступним без кваліфікації всі члени з p (це аналогічно до  import p.* в Java).

Твердження імпорту з декількома виразами імпорту import p1.I1,,pn.In інтерпретується як послідовність тверджень імпорту

            import p1.I1; ; import pn.In.

Приклад

Розглянемо визначення об'єкта:

object M {
  def z = 0, one = 1
  def add(x: Int, y: Int): Int = x + y
}

Тоді блок

{ import M.{one, z => zero, _}; add(zero, one) }

еквіваленний до блока 

{ M.add(M.z, M.one) }


7. Модифікатор implicit

LocalModifier  ::= ‘implicit’
ParamClauses   ::= {ParamClause} [nl] ‘(’ ‘implicit’ Params ‘)’

Члени шаблону та параметри, відмічені модифікатором implicit, можуть бути передані як  неявні параметри, та можуть використовуватись як неявні перетворення, що називаються переглядами. Модифікатор implicit є недійсним для всіх типованих членів, так само, як для об'єктів вищого рівня.

Наступний код визначає абстрактний клас моноїдів, та дві конкретні реалізації, StringMonoid та IntMonoid. Дві реалізації позначені як implicit.

abstract class Monoid[A] extends SemiGroup[A] {
  def unit: A
  def add(x: A, y: A): A
}
object Monoids {
  implicit object stringMonoid extends Monoid[String] {
    def add(x: String, y: String): String = x.concat(y)
    def unit: String = ""
  }
  implicit object intMonoid extends Monoid[Int] {
    def add(x: Int, y: Int): Int = x + y
    def unit: Int = 0
  }
}

Неявні параметри

Список неявних параметрів (implicit p1,,pn) маркує параметри  p1,,pn як неявні. Метод або конструктор може мати тільки один список неявних параметрів, та він мусить бути останнім з наданих списків параметрів. 

Метод з неявними параметрами може бути застосований до аргументів, так само, як звичайний метод. В цьому випадку мітка implicit не має ефекта. Однак, якщо методу бракує аргументів для його неявних параметрів, такі аргументи будуть запроваджені автоматично. 

Справжні аргументи, що придатні для передачі до неявного параметра типу T підпадають в дві категорії. Перше, придатні всі ідентифікатори  x, що можуть бути доступні в точці виклику метода, без префіксу та що позначає неявне визначення або неявний параметр. Придатний ідентифікатор може, таким чином, бути локальним ім'ям або членом обмежуючого шаблону, або він може бути зроблений без префіксу через твердження import. Якщо немає придатних ідентифікаторів, що підпадають під це правило, тоді, друге, також придатні всі implicit члени деякого об'єкту, що належить до неявної сфери неявного типу параметру, T.

Неявна сфера типу T містить всі компанійські модулі класів, що асоційовані з невяним типом параметра. Тут ми називаємо, що  клас C є асоційований з типом T, якщо він є базовим класом деякої частини T.

Частини типу T наступні:

Зауважте, що пакунки внутрішньо представлені як класи з компанійськіим модулями, що містять члени пакунку. Таким чином, неявні параметри, визначені в об'єкті пакунку, є частиною неявної сфери типу з префіксом в вигляді цього пакунку. 

Якщо існує декілька придатних аргументів, що співпадають з типами неявніх параметрів, буде обраний найбільш специфічний, з використанням правил статичного розрішення перевантаження. Якщо параметр має аргумент по замовчанню, та не може бути знайдено неявний аргумент, буде використаний аргумент по замовчанню.


Приклад

Беручи класи з прикладу Monoid, ось метод, що обчислює суму списку елементів з використанням операцій моноїда add та unit.

def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
  if (xs.isEmpty) m.unit
  else m.add(xs.head, sum(xs.tail))

Моноїд, що розглядається, помічений як неявний параметр, і, таким чином, може бути виведений, базуючись на типі списку. Розгляньте, наприклад, виклик sum(List(1, 2, 3)) в контексті, де видимі stringMonoid та intMonoid. Ми знаємо, що формальний тип параметру sum потребує бути уособлений як Int. Єдиним припустимим об'єктом, що співпадає з типом неявного формального параметра, Monoid[Int], є intMonoid, так що цей об'єкт буде переданий як неявний параметр.

Ця дискусія також показує, що неявні параметри застосовуються після виведення всіх типів аргументів.

Неявні методи можуть самі по собі мати неявні параметри. Як приклад розглянемо наступний метод з модуля scala.List, що вводить списки до класу scala.Ordered, за умови, що тип елементів також конвертується до цього типу.

implicit def list2ordered[A](x: List[A])
  (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] =
  ...

Розглянемо додавання методу

implicit def int2ordered(x: Int): Ordered[Int]

що вводить цілі в клас Ordered. Тепер ми можемо визначити метод sort над впорядкованими списками:

def sort[A](xs: List[A])(implicit a2ordered: A => Ordered[A]) = ...

Ми можемо застосувати sort до списку списків цілих yss:List[List[Int]] як наступне:

sort(yss)

Виклик вище буде завершений, передаючи два вкладені неявні аргументи:

sort(yss)(xs: List[Int] => list2ordered[Int](xs)(int2ordered)) .

Можливість передачі неявних аргументів до неявних аргументів підіймає можливість безкінечної рекурсії. Наприклад, дехто може спробувати визначити наступний метод, що вводить кожний тип в клас Ordered:

implicit def magic[A](x: A)(implicit a2ordered: A => Ordered[A]): Ordered[A] =
  a2ordered(x)

Тепер, якщо хтось спробує застосувати sort до аргументу типу arg, що не має іншого введення в клас Ordered, може утворитись безкінечна рекурсія:

sort(arg)(x => magic(x)(x => magic(x)(x => ... )))

Щоб запобігти такому безкінечному розкриттю, компілятор відслідковує стек “відкритих неявних типів”, для яких наразі відбувається пошук неявних аргументів. Коли б не розшукувався неявний аргумент для T, “ядро типу” T додається до стеку. Тут ядро типу T є T з розширеними псевдонімами, видаленими високорівневими анотаціями типів та роз'ясненнями, та з входженнями високорівневих екзестенціальних межевих замінних, заміненими на їх вищі межі. Ядро типу видаляється зі стеку, коли пошук неявних аргументів або напене схибить, або завершиться успішно. Кожного разу, коли ядро типу додається до стеку, відбувається перевірка, що цей тип не домінує над жодним іншим типом з цієї множини. 

Кажуть, що тип T домінує над типом U, якщо T є еквівалентним до U, або якщо високорівневі конструктори типу T та U мають загальний елемент, та T є більш складним, ніж U.

Набір високорівневих конструкторів типів ttcs(T) типу T залежить від форми типу:

Складністю complexity(T) типу ядра є ціле, що також залежить від форми типу:

Приклад

Коли вводимо sort(xs) для деякого списку xs типу List[List[List[Int]]], послідовність типів, для яких шукаються неявні аргументи, будуть наступними

List[List[Int]] => Ordered[List[List[Int]]],
List[Int] => Ordered[List[Int]]
Int => Ordered[Int]

Всі типи поділяють загальний конструктор типу scala.Function1, але складність кожного нового типу менша, ніж складність попереднього типу. Таким чином код перевіряє тип.


Приклад

Нехай ys буде списком деякого типу, що не може бути перетворений на Ordered. Наприклад:

val ys = List(new IllegalArgumentException, new ClassCastException, new Error)

Уявімо, що визначення magic, надане вище, знаходиться в полі зору. Тоді послідовність типів, для яких будуть шукатись неявні аргументи, буде наступна

Throwable => Ordered[Throwable],
Throwable => Ordered[Throwable],
...

Оскільки другий тип в послідовності еквівалентний першому, компілятор буде видавати помилку, повідомляючи про неявне розширення, що розходиться.


Перегляди

Неявні параметри та методи можуть також визначати неявні перетворення, що називаються переглядами. Перегляд від типу S до типу T визначений неявним значенням, що має функціональний тип S=>T або (=>S)=>T, або метод, що зводиться до значення цього типу. 

Перегляди застосовуються в трьох ситуаціях:

  1. Якщо вираз e є типу T, та T не задовільняє очікуваному типу виразу pt. В цьому випадку розшукується неявне v, що можна застосувати до e та чий тип результату задовільняє pt. Пошук відбувається як і в випадку неявних параметрів, де неявне поле зору є таке, що T => pt. Якщо знайдений такий перегляд, вираз e конвертується на v(e).
  2. В селекції e.m , де e типу T, якщо селектор m не позначає досяжного члена T. В цьому випадку шукається перегляд v, що може застосуватись до e, та чий результат має член з ім'ям m. Пошук відбувається як і в випадку неявних параметрів, де неявна сфера є одна з  T. Якщо такий перегляд буде знайдений, селекція e.m конвертується на v(e).m.
  3. В селекції e.m(args), де e типу T, якщо селектор m позначає деякі типи T, але ніякий з ціх членів не стосується до аргументів args. В цьому випадку шукається перегляд v , що може стосуватись до e, та чий результат містить метод m, який може бути застосований до args. Пошук відбувається як і в випадку неявних параметрів, де неявна сфера є одна з  T. Якщо такий перегляд знайдений, селекція e.m конвертується на v(e).m(args).

Неявний перегляд, якщо він буде знайдений, може сприйняти аргумент e як параметр викликаний-по-значенню або як викликаний-по-імені. Однак викликані-по-значенню неявні мають перевагу на викликаними-по-імені.

Як і для неявних параметрів, використовується розрішення перевантаженняв разі, коли є декілька можливих кандидатів (з будь якої категорії, викликаних-по-значенню або викликаних-по-імені).

Клас scala.Ordered[A] містить метод

  def <= [B >: A](that: B)(implicit b2ordered: B => Ordered[B]): Boolean .

Уявть два списки, xs та ys, типу List[Int], та що методи list2ordered та int2ordered, визначені тут є в сфері досяжності. Тоді операція

  xs <= ys

є легальною, та розширюється до:

  list2ordered(xs)(int2ordered).<=
    (ys)
    (xs => list2ordered(xs)(int2ordered))

Перше застосування list2ordered конвертує список xs до примірнику класу Ordered, тоді як друге трапляння є частиною неявного параметру, переданому методу <=.


Межі контексту та межі перегляду

  TypeParam ::= (id | ‘_’) [TypeParamClause] [‘>:’ Type] [‘<:’ Type]
                {‘<%’ Type} {‘:’ Type}

Типовий параметр A метода або не-трейтового класу може мати один або більше обмежень перегляду A <% T. В цьому випадку параметр типу може бути утворений до кожного типу S , що конвертується застосуванням перегляду до меж T.

Параметр типу A методу або не-трейтового класу може також мати одне або більше обмежень контексту A : T. В цьому випадку параметр типу може бути утрворений до любого типу S , для якого в точці утворення примірнику існує доказ, що S задовільняє обмеженню T. Такий доказ складається з неявного значення з типом T[S].

Метод або клас, що містить параметр типу з обмеженням перегляду або контексту, трактується як такий, що еквівалентний методу з неявними параметрами. Розглянемо перший випадок одного параметру, що обмежений по перегляду або контексту, як цей:

def f[A <% T1 ... <% Tm : U1 : Un](ps): R = ...

Тоді визначення методу вище буде розширене до такого

def f[A](ps)(implicit v1: A => T1, ..., vm: A => Tm,
                       w1: U1[A], ..., wn: Un[A]): R = ...

де vi та wj є свіжими іменами для нових введених неявних параметрів. Ці параметри називають доказовими параметрами.

Якщо клас або метод має декілька обмежень перегляду або контексту для параметрів типу, кожний такий параметр розширюється в доказові параметри, щоб вони з'явились та всі отримані доказові параметри конкатенуються в один розділ неявних параметрів. Оскільки трейти не сприймають параметрів конструктора, ця трансляція не робить для них. Відповідно, параметри-типи в трейтах не можуть бути перегляд- або контекст-обмеженими. Також, метод або клас з перегляд- або контекст- обмеженнями не може визначати жодні додаткові неявні параметри. 


Приклад

Метод <=  з приклада Ordered може бути декларований більш узгоджено наступним чином:

def <= [B >: A <% Ordered[B]](that: B): Boolean

Маніфести

Маніфести є описи типу, що можуть бути автоматично згенеровані компілятором Scala як аргументи до неявних параметрів. Стандартна бібліотека Scala містить ієрархію чотирьох класів маніфесту, з OptManifest зверху. Їх сигнатури слідують схемі нижче.

trait OptManifest[+T]
object NoManifest extends OptManifest[Nothing]
trait ClassManifest[T] extends OptManifest[T]
trait Manifest[T] extends ClassManifest[T]

Якщо неявний параметр метода або конструктора є субтипом M[T] класу OptManifest[T], маніфест є визначений для M[S], відповідно до наступних правил.

Перше, якщо вже є неявний аргумент, що співпадає з M[T], обирається цей аргумент.

Інакше, нехай Mobj буде об'єктом-компаньоном scala.reflect.Manifest, якщо M є трейтом Manifest, або компаньоном об'єкта scala.reflect.ClassManifest інакше. Нехай M буде трейтом Manifest, якщо M є трейтом Manifest, або трейтом OptManifest інакше. Тоді застосовуються такі правила.

  1. Якщо T є значенням класу, або одним з класів Any, AnyVal, Object, Null, або Nothing, маніфест для нього генерується, обираючи відповідне значення маніфесту Manifest.T, що існує в модулі Manifest.
  2. Якщо T є примірником Array[S], маніфест генерується за виклика Mobj.arrayType[S](m), де m є маніфестом, визначеним для M[S].
  3. Якщо T є деяким іншим класом типу S#C[U1,,Un] , де префікс типу S не може бути статично визначений з класу C, маніфест генерується з викликом Mobj.classType[T](m0, classOf[T], ms), де m0 є маніфестом, визначеним для M[S] , та ms є маніфести, визначені для M[U1],,M[Un].
  4. Якщо T є якогось іншого типу класу, з аргументами типу U1,,Un, маніфест генерується з викликом Mobj.classType[T](classOf[T], ms), де ms є маніфестами, визначеними для M[U1],,M[Un].
  5. Якщо T є типом-синглтоном p.type, маніфест генерується з викликом Mobj.singleType[T](p)
  6. Якщо T є уточненим типом TR, маніфест генерується для  T. (Тобто, уточнення ніколи не відображуються в маніфестах).
  7. Якщо T є перетином типу з T1 with ,, with Tn , де n>1, результат залежить від того, чи визначений повний маніфест, або ні. Якщо M є трейтом Manifest, тоді маніфест генерується з викликом  Manifest.intersectionType[T](ms), де ms маніфестами, визначеними для M[T1],,M[Tn]. Інакше, якщо M є трейтом ClassManifest, тоді маніфест генерується для домінатора перетину типів T1,,Tn.
  8. Якщо T є деяким іншим типом, тоді якщо M є трейтом OptManifest, маніфест генерується з визначника scala.reflect.NoManifest. Якщо M є типом, іншим ніж OptManifest, отримаємо статичну помилку.

8. Порівняння з шаблонами

Шаблони

  Pattern         ::=  Pattern1 { ‘|’ Pattern1 }
  Pattern1        ::=  varid ‘:’ TypePat
                    |  ‘_’ ‘:’ TypePat
                    |  Pattern2
  Pattern2        ::=  varid [‘@’ Pattern3]
                    |  Pattern3
  Pattern3        ::=  SimplePattern
                    |  SimplePattern {id [nl] SimplePattern}
  SimplePattern   ::=  ‘_’
                    |  varid
                    |  Literal
                    |  StableId
                    |  StableId ‘(’ [Patterns] ‘)’
                    |  StableId ‘(’ [Patterns ‘,’] [varid ‘@’] ‘_’ ‘*’ ‘)’
                    |  ‘(’ [Patterns] ‘)’
                    |  XmlPattern
  Patterns        ::=  Pattern {‘,’ Patterns}

Шаблон будується з констант, конструкторів, змінних та перевірок типу. Порівняння з шаблоном перевіряє, чи задане значення (або послідовність значень) має профіль, заданий в шаблоні, та, якщо це так, прив'язує змінні в шаблоні до відповідних компонент значення (або послідовності значень). Те ж ім'я змінної не може прив'язуватись більше одного разу в шаблоні.

Приклад

Деякі приклади шабловів є:

  1. Шаблон ex: IOException співпадає з усіма примірниками класу IOException, прив'язуючи ex до примірника.
  2. Шаблон Some(x) порівнює значення форми Some(v), прив'язуючи x до значення аргумента v конструктора Some.
  3. Шаблон (x, _) співпадає з парами значень, прив'язуючи x д опершого компонента пари. Другий компонент співпадає з шаблоном-підстановочним символом.
  4. Шаблон x :: y :: xs співпадає зі списками довжиною 2, прив'язуючи x до першого елементу списку, y до другого елементу, та xs до решти.
  5. Шаблон 1 | 2 | 3 співпадає з цілими між 1 та 3.

Співпадіння шаблону завжди відбувається в контексті, що постачає отримуваний тип шаблону. Ми розрізняємо наступні типи шаблонів.

Шаблон змінних

  SimplePattern   ::=  `_' |  varid

Щаблон змінної x є простим ідентифікатором, що починається з малої літери. Це відповідає любому значенню, та прив'язує ім'я змінної до цього значення. Тип x є очікуваним типом шаблона, як дане ззовні. Окремий випадк є шаблон символа підстановки _ , що розглядається так, якби це була свіжа змінна при кожному входженні.

Шаблони типу

  Pattern1        ::=  varid `:' TypePat |  `_' `:' TypePat

Шаблон типу x:T складається зі змінної x та шаблону типу T. Тип x є типовий шаблон T, де кожна змінна типу та підстановочний символзамінюються на свіжий, невідомий тип. Цей шаблон співпадає з любим значенням шаблону типу T; він прикріпляє ім'я змінні до цього значення.

Сполучення шаблонів

  Pattern2        ::=  varid `@' Pattern3

Сполучення шаблону  x@p складається зі змінної шаблону x та шаблону  p. Тип змінної x є статичним типом T шаблону p. Цей шаблон співпадає з любим значенням v , що співпадає з шаблоном  p, проваджений тип часу виконання v також є примірником T, та це прикріплює ім'я змінною до цього значення.

Літеральні шаблони

  SimplePattern   ::=  Literal

Літеральний шаблон L співпадає з любим значенням, що рівне (в термінах ==) літералу L. Тип L має задовільняти очікуваному типу шаблону.

Шаблони стабільних ідентифікаторів

  SimplePattern   ::=  StableId

Шаблон стабільного ідентифікатора є стабільний ідентифікатор r. Тип r має задовільняти очікуваному типу шаблона. Шаблон співпадає з любим значенням v , таким що r == v (дивіться тут).

Щоб розрішити синтаксичне перекриття з шаблоном змінної, шаблон стабільного ідентифікатора не може бути простим ім'ям, що починається з малої літери. Однак можливо замкнути такий ідентифікатор в зворотні лапки; тоді він буде розглядатись як шаблон стабільного ідентифікатора. 

Приклад

Розглянемо наступне визначення функції:

def f(x: Int, y: Int) = x match {
  case y => ...
}

Тут y є шаблоном змінної, що співпадає з любим значенням. Якщо ми бажаємо перетворити шаблон на шаблон стабільного ідентифікатора, це може бути зроблене наступним чином:

def f(x: Int, y: Int) = x match {
  case `y` => ...
}

Тепер шаблон співпадає з параметром y з охоплюючої функції f. Тобто співпадіння відбувається тільки якщо аргументи x та y функції f рівні.

Конструктори шаблонів

SimplePattern   ::=  StableId `(' [Patterns] `)

Шаблон конструктора є форма c(p1,,pn) де n0. Він складається з стабільного ідентифікатора c, за яким слідують елементи шаблонів p1,,pn. Конструктор c є простим або кваліфікованим ім'ям, що денотує кейс клас. Якщо цей кейс клас є мономорфічним, тоді він мусить відповідати очікуваному типу, та типи формальних параметрів  x's первинного конструктора беруться як очікувані типи шаблонів елементів  p1,,pn. Якщо кейс клас є поліморфним, тоді його параметри типу уособлюються, так що уособлення c відповідає очікуваному типу шаблону. Уособлений формальний параметр типів c первинного конструктора потім беруться як очікувані типи шаблонів компонента p1,,pn. Цей шаблон співпадає зі всіма об'єктами, створеними з виклику конструктора c(v1,,vn) , де кожний шаблон елементу pi співпадає з відповідним значенням vi.

Особливий випадок виникає, коли типи формальних параметрів  c завершуються повторюваним параметром. Ця тема розвинута тут.

Шаблони кортежів

  SimplePattern   ::=  `(' [Patterns] `)'

Шаблон кортежів (p1,,pn) є псевдонімом для конструктора шаблона scala.Tuplen(p1,,pn), де n2. Пустий кортеж () є унікальним значенням типу scala.Unit.

Шаблони екстракторів

  SimplePattern   ::=  StableId `(' [Patterns] `)'

Шаблон екстрактора x(p1,,pn) де n0 є тією ж синтаксичною формою, що і шаблон конструктора. Однак, замість кейс класу, стабільний ідентифікатор x позначає об'єкт, що має член-метод з ім'ям  unapply або unapplySeq, що співпадає з шаблоном.

Метод unapply в об'єкті x співпадає з шаблоном x(p1,,pn) , якщо він приймає точно один аргумент, та виконується одне з наступного:

Метод unapplySeq в об'єкті x співпадає з шаблоном x(q1,,qm,p1,,pn) , якщо він сприймає рівно один аргумент, та його тип результата має форму Option[(T1,,Tm, Seq[S])] (якщо m = 0, тип Option[Seq[S]] також придатний). Цей випадок далі дискутується нижче.

Приклад

Об'єкт Predef містить визначення об'єкта екстрактора Pair:

object Pair {
  def apply[A, B](x: A, y: B) = Tuple2(x, y)
  def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x)
}

Це означає, що ім'я Pair може використовуватись замість Tuple2 для інформаціїї про кортеж, так само, як для деконструкції кортежів в шаблонах. Таким чином, можливе наступне:

val x = (1, 2)
val y = x match {
  case Pair(i, s) => Pair(s + i, i * i)
}

Шаблони послідовностей

SimplePattern ::= StableId `(' [Patterns `,'] [varid `@'] `_' `*' `)'

Шаблон послідовності p1,,pn з'являється в двох контекстах. Перше, в шаблоні конструктора  c(q1,,qm,p1,,pn), де c є кейс класом, що має m+1 параметрів первинного конструктора, що завершується на повторюваний параметр типу S*. Друге, в шаблоні екстрактора x(q1,,qm,p1,,pn) , якщо об'єкт екстрактора x не має метода unapply, вле він визначає метод unapplySeq з типом результата, що задовільняє Option[(T_1, ... , T_m, Seq[S])] (якщо m = 0, тип  Option[Seq[S]] також прийнятний). Очікуваний тип для шаблонів pi є S.

Останній шаблон в послідовності шаблонів може бути підстановочним символом послідовності  _*. Кожний елемент шаблону pi є тип-перевіреним з S в якості очікуваного типу, якщо це не підстановочний символ. Якщо оитанній підстановочний символ послідовності присутній, шаблон співпадає з усіма значеннями v, що є послідовностями, що починаються з елементів, співпадаючих з шаблонами  p1,,pn1. Якщо не надано останнього підстановочного символу послідовності, щаблон співпадає з усіма значеннями v, що є послідовностями довжини n, що склалаються з елементів, співпадаючих з шаблонами p1,,pn.

Шаблони інфіксних операцій

  Pattern3  ::=  SimplePattern {id [nl] SimplePattern}

Шаблон інфіксної операції p;op;q є скороченням для шаблонів конструктора або екстрактора op(p,q). Приорітетність та асоциативність операторів в шаблонах такі самі, як в виразах.

Шаблон інфіксної операції  p;op;(q1,,qn) є скороченням для шаблону конструктора або екстрактора op(p,q1,,qn).

Альтернатива шаблонів

  Pattern   ::=  Pattern1 { `|' Pattern1 }

Альтернатива шаблонів p1 | | pn складається з числа альтернативних шаблонів pi. Всі альтернативні шаблони є тип-перевіреними з очікуванним типом шаблону. Вони не можуть прив'язувати інші змінні, окрім підстановочних символів. Альтернативний шаблон співпадає зі значенням v , коли хоча б одна з його альтернатив співпадає з v.

XML шаблони

XML шаблони розглядаються тут.

Шаблони регулярних виразів

Шаблони регулярних виразів були припинені в Scala починаючи з версії 2.0.

Пізніші версії Scala провадять значно спрощену версію шаблонів регулярних виразів, що покривають більшість сценаріїїв для обробки не-текстових послідовностей. Шаблон послідовності є шаблоном, що займає це місце, коли або (1) очікується шаблон типу T, який відповідає до Seq[A] для деякого A, або (2) конструктор кейс класу, що має ітерований формальний параметр A*. Шаблон підстановочної зірочки  _* в самій правій позиції стоїть замість послідовностей довільної довжини. Він може бути пров'язаний до змінних з використанням @, як звичайно, і в такому випадку змінна буде мати тип Seq[A].

Незаперечні шаблони

Шаблон p є незаперечним для типу T, якщо виконується одне з наступного:

  1. p є змінним шаблоном,
  2. p є типізованим шаблоном x:T, та T<:T,
  3. p є шаблоном конструктора c(p1,,pn), тип T є примірником класу cпервинний конструктор типу T має аргумент типів T1,,Tn, та кожний  pi є незаперечним для Ti.

Шаблони типу

  TypePat           ::=  Type

Шаблони типу складаються з типів, змінних типів, та підстановочних символів. Шаблон типу T є одною з наступних форм:

The bottom types scala.Nothing and scala.Null cannot be used as type patterns, because they would match nothing in any case.

Types which are not of one of the forms described above are also accepted as type patterns. However, such type patterns will be translated to their erasure. The Scala compiler will issue an "unchecked" warning for these patterns to flag the possible loss of type-safety.

A type variable pattern is a simple identifier which starts with a lower case letter.

Type Parameter Inference in Patterns

Type parameter inference is the process of finding bounds for the bound type variables in a typed pattern or constructor pattern. Inference takes into account the expected type of the pattern.

Type parameter inference for typed patterns

Assume a typed pattern p:T. Let T result from T where all wildcards in T are renamed to fresh variable names. Let a1,,anbe the type variables in T. These type variables are considered bound in the pattern. Let the expected type of the pattern be pt.

Type parameter inference constructs first a set of subtype constraints over the type variables ai. The initial constraints set C0reflects just the bounds of these type variables. That is, assuming T has bound type variables a1,,an which correspond to class type parameters a1,,an with lower bounds L1,,Ln and upper bounds U1,,Un, C0 contains the constraints

{aiσLi<:σUi<:ai(i=1,,n)(i=1,,n)

where σ is the substitution [a1:=a1,,an:=an].

The set C0 is then augmented by further subtype constraints. There are two cases.

Case 1

If there exists a substitution σ over the type variables ai,,an such that σT conforms to pt, one determines the weakest subtype constraints C1 over the type variables a1,,an such that C0C1 implies that T conforms to pt.

Case 2

Otherwise, if T can not be made to conform to pt by instantiating its type variables, one determines all type variables in pt which are defined as type parameters of a method enclosing the pattern. Let the set of such type parameters be b1,,bm. Let C0 be the subtype constraints reflecting the bounds of the type variables bi. If T denotes an instance type of a final class, let C2 be the weakest set of subtype constraints over the type variables a1,,an and b1,,bm such that C0C0C2 implies that Tconforms to pt. If T does not denote an instance type of a final class, let C2 be the weakest set of subtype constraints over the type variables a1,,an and b1,,bm such that C0C0C2 implies that it is possible to construct a type T which conforms to both T and pt. It is a static error if there is no satisfiable set of constraints C2 with this property.

The final step consists in choosing type bounds for the type variables which imply the established constraint system. The process is different for the two cases above.

Case 1

We take ai>:Li<:Ui where each Li is minimal and each Ui is maximal wrt <: such that ai>:Li<:Ui for i=1,,nimplies C0C1.

Case 2

We take ai>:Li<:Ui and bi>:Li<:Ui where each Li and Lj is minimal and each Ui and Uj is maximal such that ai>:Li<:Ui for i=1,,n and bj>:Lj<:Uj for j=1,,m implies C0C0C2.

In both cases, local type inference is permitted to limit the complexity of inferred bounds. Minimality and maximality of types have to be understood relative to the set of types of acceptable complexity.

Type parameter inference for constructor patterns

Assume a constructor pattern C(p1,,pn) where class C has type type parameters a1,,an. These type parameters are inferred in the same way as for the typed pattern (_: C[a1,,an]).

Example

Consider the program fragment:

val x: Any
x match {
  case y: List[a] => ...
}

Here, the type pattern List[a] is matched against the expected type Any. The pattern binds the type variable a. Since List[a]conforms to Any for every type argument, there are no constraints on a. Hence, a is introduced as an abstract type with no bounds. The scope of a is right-hand side of its case clause.

On the other hand, if x is declared as

val x: List[List[String]],

this generates the constraint List[a] <: List[List[String]], which simplifies to a <: List[String], because List is covariant. Hence, a is introduced with upper bound List[String].

Example

Consider the program fragment:

val x: Any
x match {
  case y: List[String] => ...
}

Scala does not maintain information about type arguments at run-time, so there is no way to check that x is a list of strings. Instead, the Scala compiler will erase the pattern to List[_]; that is, it will only test whether the top-level runtime-class of the valuex conforms to List, and the pattern match will succeed if it does. This might lead to a class cast exception later on, in the case where the list x contains elements other than strings. The Scala compiler will flag this potential loss of type-safety with an "unchecked" warning message.

Example

Consider the program fragment

class Term[A]
class Number(val n: Int) extends Term[Int]
def f[B](t: Term[B]): B = t match {
  case y: Number => y.n
}

The expected type of the pattern y: Number is Term[B]. The type Number does not conform to Term[B]; hence Case 2 of the rules above applies. This means that B is treated as another type variable for which subtype constraints are inferred. In our case the applicable constraint is Number <: Term[B], which entails B = Int. Hence, B is treated in the case clause as an abstract type with lower and upper bound Int. Therefore, the right hand side of the case clause, y.n, of type Int, is found to conform to the function's declared result type, Number.

Pattern Matching Expressions

  Expr            ::=  PostfixExpr `match' `{' CaseClauses `}'
  CaseClauses     ::=  CaseClause {CaseClause}
  CaseClause      ::=  `case' Pattern [Guard] `=>' Block

A pattern matching expression

e match { case p1 => b1  case pn => bn }

consists of a selector expression e and a number n>0 of cases. Each case consists of a (possibly guarded) pattern pi and a block bi. Each pi might be complemented by a guard if e where e is a boolean expression. The scope of the pattern variables in picomprises the pattern's guard and the corresponding block bi.

Let T be the type of the selector expression e and let a1,,am be the type parameters of all methods enclosing the pattern matching expression. For every ai, let Li be its lower bound and Ui be its higher bound. Every pattern pp1,,,pn can be typed in two ways. First, it is attempted to type p with T as its expected type. If this fails, p is instead typed with a modified expected type T which results from T by replacing every occurrence of a type parameter ai by \mbox{\sl undefined}. If this second step fails also, a compile-time error results. If the second step succeeds, let Tp be the type of pattern p seen as an expression. One then determines minimal bounds L11,,Lm and maximal bounds U1,,Um such that for all i, Li<:Liand Ui<:Ui and the following constraint system is satisfied:

L1<:a1<:U1Lm<:am<:Um  Tp<:T

If no such bounds can be found, a compile time error results. If such bounds are found, the pattern matching clause starting with pis then typed under the assumption that each ai has lower bound Li instead of Li and has upper bound Ui instead of Ui.

The expected type of every block bi is the expected type of the whole pattern matching expression. The type of the pattern matching expression is then the weak least upper bound of the types of all blocks bi.

When applying a pattern matching expression to a selector value, patterns are tried in sequence until one is found which matches the selector value. Say this case is case pibi. The result of the whole expression is the result of evaluating bi, where all pattern variables of pi are bound to the corresponding parts of the selector value. If no matching pattern is found, a scala.MatchErrorexception is thrown.

The pattern in a case may also be followed by a guard suffix if e with a boolean expression e. The guard expression is evaluated if the preceding pattern in the case matches. If the guard expression evaluates to true, the pattern match succeeds as normal. If the guard expression evaluates to false, the pattern in the case is considered not to match and the search for a matching pattern continues.

In the interest of efficiency the evaluation of a pattern matching expression may try patterns in some other order than textual sequence. This might affect evaluation through side effects in guards. However, it is guaranteed that a guard expression is evaluated only if the pattern it guards matches.

If the selector of a pattern match is an instance of a sealed class, the compilation of pattern matching can emit warnings which diagnose that a given set of patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.

Example

Consider the following definitions of arithmetic terms:

abstract class Term[T]
case class Lit(x: Int) extends Term[Int]
case class Succ(t: Term[Int]) extends Term[Int]
case class IsZero(t: Term[Int]) extends Term[Boolean]
case class If[T](c: Term[Boolean],
                 t1: Term[T],
                 t2: Term[T]) extends Term[T]

There are terms to represent numeric literals, incrementation, a zero test, and a conditional. Every term carries as a type parameter the type of the expression it represents (either Int or Boolean).

A type-safe evaluator for such terms can be written as follows.

def eval[T](t: Term[T]): T = t match {
  case Lit(n)        => n
  case Succ(u)       => eval(u) + 1
  case IsZero(u)     => eval(u) == 0
  case If(c, u1, u2) => eval(if (eval(c)) u1 else u2)
}

Note that the evaluator makes crucial use of the fact that type parameters of enclosing methods can acquire new bounds through pattern matching.

For instance, the type of the pattern in the second case, Succ(u), is Int. It conforms to the selector type T only if we assume an upper and lower bound of Int for T. Under the assumption Int <: T <: Int we can also verify that the type right hand side of the second case, Int conforms to its expected type, T.

Pattern Matching Anonymous Functions

  BlockExpr ::= `{' CaseClauses `}'

An anonymous function can be defined by a sequence of cases

{ case p1 => b1  case pn => bn }

which appear as an expression without a prior match. The expected type of such an expression must in part be defined. It must be either scala.Functionk[S1,,Sk, R] for some k>0, or scala.PartialFunction[S1, R], where the argument type(s) S1,,Sk must be fully determined, but the result type R may be undetermined.

If the expected type is scala.Functionk[S1,,Sk, R], the expression is taken to be equivalent to the anonymous function:

(x1:S1,,xk:Sk) => (x1,,xk) match {
  case p1 => b1  case pn => bn
}

Here, each xi is a fresh name. As was shown here, this anonymous function is in turn equivalent to the following instance creation expression, where T is the weak least upper bound of the types of all bi.

new scala.Functionk[S1,,Sk, T] {
  def apply(x1:S1,,xk:Sk): T = (x1,,xk) match {
    case p1 => b1  case pn => bn
  }
}

If the expected type is scala.PartialFunction[S, R], the expression is taken to be equivalent to the following instance creation expression:

new scala.PartialFunction[S, T] {
  def apply(x: S): T = x match {
    case p1 => b1  case pn => bn
  }
  def isDefinedAt(x: S): Boolean = {
    case p1 => true  case pn => true
    case _ => false
  }
}

Тут x є свіжим ім'ям та T є слабким найменьшим вищим обмеженнямтипів всіх bi. Фінальний випадок по замовчанню в методі isDefinedAt опущений, якщо один з шаблонів p1,,pn вже змінний або підстановочний шаблон.

Приклад

Ось метод, що використовує операцію лівої згортки /: для обчислення скалярного добутку двох векторів:

def scalarProduct(xs: Array[Double], ys: Array[Double]) =
  (0.0 /: (xs zip ys)) {
    case (a, (b, c)) => a + b * c
  }

Кейс твердження в цьому коді еквіваленті до наступної анонімної функції:

(x, y) => (x, y) match {
  case (a, (b, c)) => a + b * c
}